mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
fix(sandbox): bind parent dir for globstar allow_write on bwrap
Fine-grained per-path mounts under read-only project binds broke pip install into in-project .venv directories. Always mount the parent tree for ** write rules instead. Fixes #315
This commit is contained in:
@@ -529,8 +529,19 @@ func (t *bubblewrapPolicyTranslator) processWriteRule(path string, boundPaths ma
|
||||
|
||||
// Check if path contains glob pattern
|
||||
if util.ContainsGlob(path) {
|
||||
// Check if the base directory is already bound (e.g., /tmp already bound, skip /tmp/**)
|
||||
baseDir := t.extractParentDir(path)
|
||||
|
||||
// Globstar write rules always bind the parent directory read-write. Per-path
|
||||
// binds interact badly with earlier read-only parent mounts (e.g. ${CWD}/**)
|
||||
// and miss files beyond maxGlobDepth — see https://github.com/safedep/pmg/issues/315.
|
||||
if strings.Contains(path, "**") {
|
||||
args = append(args, "--bind-try", baseDir, baseDir)
|
||||
boundPaths[baseDir] = true
|
||||
log.Debugf("Globstar allow_write: bound parent directory '%s' (read-write)", baseDir)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Check if the base directory is already bound (e.g., /tmp already bound, skip /tmp/**)
|
||||
if boundPaths[baseDir] {
|
||||
log.Debugf("Skipping pattern '%s' - base directory '%s' already bound", path, baseDir)
|
||||
return args, nil
|
||||
@@ -545,14 +556,9 @@ func (t *bubblewrapPolicyTranslator) processWriteRule(path string, boundPaths ma
|
||||
if useFallback {
|
||||
// Coarse-grained: bind parent directory
|
||||
for _, parentDir := range paths {
|
||||
if !boundPaths[parentDir] {
|
||||
args = append(args, "--bind-try", parentDir, parentDir)
|
||||
boundPaths[parentDir] = true
|
||||
log.Debugf("Coarse-grained fallback: bound parent directory '%s' (read-write)", parentDir)
|
||||
} else {
|
||||
// Path already bound, skip (likely already bound as read-only from essential paths)
|
||||
log.Debugf("Parent directory '%s' already bound, skipping duplicate bind", parentDir)
|
||||
}
|
||||
args = append(args, "--bind-try", parentDir, parentDir)
|
||||
boundPaths[parentDir] = true
|
||||
log.Debugf("Coarse-grained fallback: bound parent directory '%s' (read-write)", parentDir)
|
||||
}
|
||||
} else {
|
||||
// Fine-grained: bind individual paths
|
||||
|
||||
@@ -775,6 +775,42 @@ func TestGlobFallbackThresholdGlobstar(t *testing.T) {
|
||||
assert.Less(t, len(args), 300, "Coarse-grained fallback should prevent argument explosion")
|
||||
}
|
||||
|
||||
// TestBubblewrapAllowWriteGlobstarBindsParentOnly verifies globstar allow_write rules
|
||||
// bind the parent tree (e.g. .venv) instead of per-file mounts that break pip in-project venvs.
|
||||
// See https://github.com/safedep/pmg/issues/315
|
||||
func TestBubblewrapAllowWriteGlobstarBindsParentOnly(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
venvDir := filepath.Join(tmpDir, ".venv")
|
||||
binDir := filepath.Join(venvDir, "bin")
|
||||
require.NoError(t, os.MkdirAll(binDir, 0755))
|
||||
pythonPath := filepath.Join(binDir, "python")
|
||||
require.NoError(t, os.Symlink("/usr/bin/python3", pythonPath))
|
||||
|
||||
// Populate enough shallow files that old logic would fine-grain bind without hitting fallback.
|
||||
for i := 0; i < 30; i++ {
|
||||
path := filepath.Join(venvDir, fmt.Sprintf("file%d.txt", i))
|
||||
require.NoError(t, os.WriteFile(path, []byte("x"), 0644))
|
||||
}
|
||||
|
||||
config := newDefaultBubblewrapConfig()
|
||||
translator := newBubblewrapPolicyTranslator(config)
|
||||
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Name: "test-venv-write",
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{tmpDir + "/**"},
|
||||
AllowWrite: []string{venvDir + "/**"},
|
||||
},
|
||||
}
|
||||
|
||||
args, err := translator.translate(policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
argsStr := argSliceToString(args)
|
||||
assert.Contains(t, argsStr, venvDir)
|
||||
assert.NotContains(t, argsStr, pythonPath)
|
||||
}
|
||||
|
||||
// TestGlobNoFallbackSmallPattern tests that small patterns don't trigger fallback
|
||||
func TestGlobNoFallbackSmallPattern(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
Reference in New Issue
Block a user