Refuse roots and launch dirs overlapping forbidden system dirs
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

This commit is contained in:
2026-10-24 17:49:13 +11:00
parent f60caa7e98
commit 80c90769fd
2 changed files with 21 additions and 7 deletions
+11 -6
View File
@@ -369,8 +369,8 @@ func allowedRoots(home string) (roots []string, def bool, err error) {
return nil, false, fmt.Errorf("%s: %q is a dot-directory of $HOME", allowedPathsFile(), l)
}
for _, bad := range forbiddenRoots {
if inside(r, bad) {
return nil, false, fmt.Errorf("%s: %q is under %s", allowedPathsFile(), l, bad)
if bad = realpath(bad); inside(r, bad) || inside(bad, r) {
return nil, false, fmt.Errorf("%s: %q overlaps %s", allowedPathsFile(), l, bad)
}
}
for _, d := range pathDirs() {
@@ -398,9 +398,9 @@ func pathDirs() []string {
}
// checkLaunchDir returns pwd resolved if it is strictly under an allowed root,
// never $HOME itself or anything holding the sandbox's config, the API socket
// or a $PATH entry. Under the default $HOME root, dot-dirs of $HOME are
// refused too.
// never $HOME itself, anything holding the sandbox's config, the API socket or
// a $PATH entry, nor anything overlapping a forbidden system dir. Under the
// default $HOME root, dot-dirs of $HOME are refused too.
func checkLaunchDir(pwd, allowlist string) (string, error) {
pwd = realpath(pwd)
home := realpath(os.Getenv("HOME"))
@@ -416,11 +416,16 @@ func checkLaunchDir(pwd, allowlist string) (string, error) {
}
paths := allowedPathsFile()
for _, dir := range append([]string{configDir(), filepath.Dir(allowlist), filepath.Dir(realpath(allowlist)),
filepath.Dir(paths), filepath.Dir(realpath(paths)), runtimeDir()}, pathDirs()...) {
filepath.Dir(realpath(paths)), runtimeDir()}, pathDirs()...) {
if dir = realpath(dir); inside(dir, pwd) {
return "", fmt.Errorf("refusing to mount %s: it contains %s", pwd, dir)
}
}
for _, bad := range forbiddenRoots {
if bad = realpath(bad); inside(pwd, bad) || inside(bad, pwd) {
return "", fmt.Errorf("refusing to mount %s: it overlaps %s", pwd, bad)
}
}
for _, r := range roots {
if pwd != r && inside(pwd, r) {
return pwd, nil
+10 -1
View File
@@ -325,6 +325,7 @@ func TestLaunchRefusals(t *testing.T) {
writePaths(t, e, "~/src")
}},
{"invalid-root", "home/proj", func(t *testing.T, e *env) { writePaths(t, e, "/run/x") }},
{"var-tmp-under-var-root", "/var/tmp", func(t *testing.T, e *env) { writePaths(t, e, "/var") }},
{"default-root-dot-dir", "home/.local/src/x", func(t *testing.T, e *env) {
_ = os.MkdirAll(e.work+"/home/.local/src/x", 0o755)
}},
@@ -359,6 +360,11 @@ func TestCheckLaunchDirRuntimeFallback(t *testing.T) {
}
t.Setenv("HOME", "/tmp/home")
_, err = checkLaunchDir("/tmp/home/proj", "/nonexistent/hosts")
check(t, "forbidden default root", true, err != nil && strings.Contains(err.Error(), "overlaps "+realpath("/tmp")))
origRoots := forbiddenRoots
forbiddenRoots = []string{"/run"}
t.Cleanup(func() { forbiddenRoots = origRoots })
_, err = checkLaunchDir("/tmp/home/proj", "/nonexistent/hosts")
check(t, "subdir", nil, err)
}
@@ -591,11 +597,14 @@ func TestCheckLaunchDirRoots(t *testing.T) {
}
for _, bad := range []string{"/", "/home", "~", "~/", "~/.local", "~/.local/share", "~/.ssh", "~/.ssh/x",
"~/.gnupg", "~/.config/foo", cfg, cfg + "/agent-sandbox/x", "~/.local/share/containers/s", "relative/x", "/run/user/1000", "/tmp/x", "/var/tmp", "/proc/1",
"/sys", "/dev/shm", "/usr", "/srv/tools"} {
"/sys", "/dev/shm", "/usr", "/srv/tools", "/var"} {
write("/srv/work\n" + bad)
_, _, err := allowedRoots("/home/u")
check(t, "invalid root "+bad, true, strings.Contains(fmt.Sprint(err), "allowed-paths"))
}
write("/var")
_, err := checkLaunchDir("/var/tmp/x", "/nowhere/hosts")
check(t, "/var/tmp under /var root", true, err != nil)
}
func TestLaunchPassesArgsVerbatim(t *testing.T) {