From 588ef2f8c2436c80beb9a18874c2d008a478b829 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sat, 24 Oct 2026 20:43:16 +1100 Subject: [PATCH] Test launch-dir refusals not covered elsewhere --- main_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/main_test.go b/main_test.go index 7f9c20d..0bc3a29 100644 --- a/main_test.go +++ b/main_test.go @@ -681,3 +681,82 @@ func TestLaunchMountsResolvedSource(t *testing.T) { } } } + +func TestCheckLaunchDirDefaultRootForbidden(t *testing.T) { + t.Setenv("HOME", "/tmp/home") + t.Setenv("XDG_CONFIG_HOME", "/nonexistent/cfg") + t.Setenv("XDG_DATA_HOME", "") + t.Setenv("XDG_RUNTIME_DIR", "/nonexistent/rt") + t.Setenv("PATH", "/usr/bin") + _, err := checkLaunchDir("/tmp/home/proj", "/nonexistent/hosts") + check(t, "forbidden default root", "refusing to mount /tmp/home/proj: it overlaps "+realpath("/tmp"), fmt.Sprint(err)) +} + +// isolatedHome points every sensitive dir outside a fresh $HOME, leaving only +// the rule under test to refuse. +func isolatedHome(t *testing.T) (work, home string) { + work, _ = filepath.EvalSymlinks(t.TempDir()) + elsewhere, _ := filepath.EvalSymlinks(t.TempDir()) + home = work + "/home" + for _, d := range []string{".ssh", ".gnupg", ".config", ".local/share/containers"} { + _ = os.MkdirAll(filepath.Join(elsewhere, d), 0o755) + _ = os.MkdirAll(filepath.Dir(filepath.Join(home, d)), 0o755) + _ = os.Symlink(filepath.Join(elsewhere, d), filepath.Join(home, d)) + } + t.Setenv("HOME", home) + t.Setenv("XDG_CONFIG_HOME", elsewhere+"/cfg") + t.Setenv("XDG_DATA_HOME", "") + t.Setenv("XDG_RUNTIME_DIR", elsewhere+"/rt") + t.Setenv("PATH", "/usr/bin") + origRoots := forbiddenRoots + forbiddenRoots = []string{"/run"} + t.Cleanup(func() { forbiddenRoots = origRoots }) + _ = os.MkdirAll(filepath.Dir(allowedPathsFile()), 0o755) + return work, home +} + +func TestCheckLaunchDirResolvedAllowedPathsDir(t *testing.T) { + _, home := isolatedHome(t) + dot := home + "/src/dot" + _ = os.MkdirAll(dot, 0o755) + _ = os.WriteFile(dot+"/allowed-paths", []byte("~/src\n"), 0o644) + _ = os.Symlink(dot+"/allowed-paths", allowedPathsFile()) + _ = os.MkdirAll(home+"/src/other", 0o755) + _, err := checkLaunchDir(home+"/src/other", "/nonexistent/hosts") + check(t, "sibling allowed", nil, err) + _, err = checkLaunchDir(dot, "/nonexistent/hosts") + check(t, "resolved allowed-paths dir", "refusing to mount "+dot+": it contains "+dot, fmt.Sprint(err)) +} + +func TestAllowedRootsRejectsHomeAndDotDirs(t *testing.T) { + work, home := isolatedHome(t) + f := allowedPathsFile() + for _, tc := range []struct{ root, want string }{ + {"~/.cache", `"~/.cache" is a dot-directory of $HOME`}, + {"~", `"~" is or contains $HOME`}, + {home, `"` + home + `" is or contains $HOME`}, + {work, `"` + work + `" is or contains $HOME`}, + } { + _ = os.WriteFile(f, []byte(tc.root+"\n"), 0o644) + _, _, err := allowedRoots(home) + check(t, tc.root, f+": "+tc.want, fmt.Sprint(err)) + } + _ = os.WriteFile(f, []byte("~/src\n"), 0o644) + _, _, err := allowedRoots(home) + check(t, "plain root", nil, err) +} + +func TestLaunchRefusesColonInResolvedSource(t *testing.T) { + e := setup(t) + _ = os.Mkdir(e.work+"/home/a:b", 0o755) + _ = os.Symlink(e.work+"/home/a:b", e.work+"/home/link") + stderr, _ := os.CreateTemp(t.TempDir(), "stderr") + orig := os.Stderr + os.Stderr = stderr + code := e.launch(t, e.work+"/home/link", "--exec", "true") + os.Stderr = orig + out, _ := os.ReadFile(stderr.Name()) + check(t, "exit", 1, code) + check(t, "stderr", "agent-sandbox: cannot mount a directory containing ':'\n", string(out)) + check(t, "podman calls", []string(nil), e.fake.calls) +} -- 2.47.3