Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

knotserver/sandbox: add tests

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

author
Anirudh Oppiliappan
committer
Tangled
date (Jun 12, 2026, 12:22 PM +0300) commit b2472281 parent 477659de change-id wnlpyyno
+392
+162
knotserver/sandbox/repofs_test.go
··· 1 + package sandbox 2 + 3 + import ( 4 + "os" 5 + "path/filepath" 6 + "runtime" 7 + "testing" 8 + ) 9 + 10 + func TestChmodRepoTree(t *testing.T) { 11 + root := t.TempDir() 12 + 13 + // build a tree: 14 + // root/ 15 + // file.txt (0644) 16 + // script.sh (0755) 17 + // subdir/ 18 + // nested.txt (0644) 19 + // link -> ../file.txt 20 + mustWrite(t, filepath.Join(root, "file.txt"), 0644, "hello") 21 + mustWrite(t, filepath.Join(root, "script.sh"), 0755, "#!/bin/sh\n") 22 + mustMkdir(t, filepath.Join(root, "subdir"), 0755) 23 + mustWrite(t, filepath.Join(root, "subdir", "nested.txt"), 0644, "nested") 24 + mustSymlink(t, "../file.txt", filepath.Join(root, "subdir", "link")) 25 + 26 + if err := ChmodRepoTree(root); err != nil { 27 + t.Fatalf("ChmodRepoTree: %v", err) 28 + } 29 + 30 + cases := []struct { 31 + path string 32 + wantMode os.FileMode 33 + }{ 34 + {root, 0770}, 35 + {filepath.Join(root, "file.txt"), 0660}, 36 + {filepath.Join(root, "script.sh"), 0770}, 37 + {filepath.Join(root, "subdir"), 0770}, 38 + {filepath.Join(root, "subdir", "nested.txt"), 0660}, 39 + } 40 + for _, c := range cases { 41 + info, err := os.Stat(c.path) 42 + if err != nil { 43 + t.Errorf("stat %s: %v", c.path, err) 44 + continue 45 + } 46 + if got := info.Mode().Perm(); got != c.wantMode { 47 + t.Errorf("%s: mode = %o, want %o", c.path, got, c.wantMode) 48 + } 49 + } 50 + } 51 + 52 + func TestChmodRepoTree_PreservesExecutableBit(t *testing.T) { 53 + root := t.TempDir() 54 + mustWrite(t, filepath.Join(root, "exec"), 0744, "") 55 + mustWrite(t, filepath.Join(root, "noexec"), 0644, "") 56 + 57 + if err := ChmodRepoTree(root); err != nil { 58 + t.Fatalf("ChmodRepoTree: %v", err) 59 + } 60 + 61 + if got := mode(t, filepath.Join(root, "exec")); got != 0770 { 62 + t.Errorf("exec file: mode = %o, want 0770", got) 63 + } 64 + if got := mode(t, filepath.Join(root, "noexec")); got != 0660 { 65 + t.Errorf("noexec file: mode = %o, want 0660", got) 66 + } 67 + } 68 + 69 + func TestChownRepoTree_SelfChown(t *testing.T) { 70 + // Chowning to our own UID/GID is always a no-op success. This verifies 71 + // the walk visits all entries without erroring. 72 + root := t.TempDir() 73 + mustWrite(t, filepath.Join(root, "a"), 0644, "") 74 + mustMkdir(t, filepath.Join(root, "b"), 0755) 75 + mustWrite(t, filepath.Join(root, "b", "c"), 0644, "") 76 + 77 + uid := os.Getuid() 78 + gid := os.Getgid() 79 + if err := ChownRepoTree(root, uid, gid); err != nil { 80 + t.Fatalf("ChownRepoTree: %v", err) 81 + } 82 + 83 + // verify everything still belongs to us. 84 + for _, p := range []string{root, filepath.Join(root, "a"), filepath.Join(root, "b"), filepath.Join(root, "b", "c")} { 85 + info, err := os.Stat(p) 86 + if err != nil { 87 + t.Fatalf("stat %s: %v", p, err) 88 + } 89 + _ = info 90 + } 91 + } 92 + 93 + func TestLookupUIDForRepoPath(t *testing.T) { 94 + if runtime.GOOS == "windows" { 95 + t.Skip("uid/gid lookup is unix-only") 96 + } 97 + scan := t.TempDir() 98 + repo := filepath.Join(scan, "did:plc:abc") 99 + mustMkdir(t, repo, 0700) 100 + 101 + uid, gid, err := LookupUIDForRepoPath(scan, repo) 102 + if err != nil { 103 + t.Fatalf("LookupUIDForRepoPath: %v", err) 104 + } 105 + if uid != uint32(os.Getuid()) { 106 + t.Errorf("uid = %d, want %d", uid, os.Getuid()) 107 + } 108 + if gid != uint32(os.Getgid()) { 109 + t.Errorf("gid = %d, want %d", gid, os.Getgid()) 110 + } 111 + } 112 + 113 + func TestLookupUIDForRepoPath_OutsideScanPath(t *testing.T) { 114 + _, _, err := LookupUIDForRepoPath("/home/git", "/etc/passwd") 115 + if err == nil { 116 + t.Fatal("expected error for path outside scan path, got nil") 117 + } 118 + } 119 + 120 + func TestLookupUIDForRepoPath_NonexistentPath(t *testing.T) { 121 + scan := t.TempDir() 122 + _, _, err := LookupUIDForRepoPath(scan, filepath.Join(scan, "does-not-exist")) 123 + if err == nil { 124 + t.Fatal("expected error for nonexistent path, got nil") 125 + } 126 + } 127 + 128 + // helpers 129 + 130 + func mustWrite(t *testing.T, path string, mode os.FileMode, content string) { 131 + t.Helper() 132 + if err := os.WriteFile(path, []byte(content), mode); err != nil { 133 + t.Fatalf("write %s: %v", path, err) 134 + } 135 + // WriteFile respects existing mode on overwrite; chmod to be sure. 136 + if err := os.Chmod(path, mode); err != nil { 137 + t.Fatalf("chmod %s: %v", path, err) 138 + } 139 + } 140 + 141 + func mustMkdir(t *testing.T, path string, mode os.FileMode) { 142 + t.Helper() 143 + if err := os.MkdirAll(path, mode); err != nil { 144 + t.Fatalf("mkdir %s: %v", path, err) 145 + } 146 + } 147 + 148 + func mustSymlink(t *testing.T, target, link string) { 149 + t.Helper() 150 + if err := os.Symlink(target, link); err != nil { 151 + t.Fatalf("symlink %s -> %s: %v", link, target, err) 152 + } 153 + } 154 + 155 + func mode(t *testing.T, path string) os.FileMode { 156 + t.Helper() 157 + info, err := os.Stat(path) 158 + if err != nil { 159 + t.Fatalf("stat %s: %v", path, err) 160 + } 161 + return info.Mode().Perm() 162 + }
+175
knotserver/sandbox/sandbox_linux_test.go
··· 1 + //go:build linux 2 + 3 + package sandbox 4 + 5 + import ( 6 + "os/exec" 7 + "strings" 8 + "syscall" 9 + "testing" 10 + ) 11 + 12 + func TestLandlockBackend_Name(t *testing.T) { 13 + if (&LandlockBackend{}).Name() != "landlock" { 14 + t.Error("Name should return \"landlock\"") 15 + } 16 + } 17 + 18 + func TestLandlockBackend_WrapMulti_NoPaths(t *testing.T) { 19 + sb := &LandlockBackend{selfExe: "/proc/self/exe"} 20 + cmd := exec.Command("git", "status") 21 + 22 + wrapped, err := sb.WrapMulti(nil, cmd) 23 + if err != nil { 24 + t.Fatalf("WrapMulti: %v", err) 25 + } 26 + if wrapped != cmd { 27 + t.Error("empty paths should return the original cmd unchanged") 28 + } 29 + } 30 + 31 + func TestLandlockBackend_WrapMulti_ArgsConstruction(t *testing.T) { 32 + sb := &LandlockBackend{selfExe: "/path/to/knot"} 33 + cmd := exec.Command("git", "upload-pack", "--stateless-rpc", ".") 34 + cmd.Env = []string{"GIT_PROTOCOL=version=2"} 35 + cmd.Stdin = strings.NewReader("input") 36 + 37 + wrapped, err := sb.WrapMulti([]string{"/repos/a", "/repos/b"}, cmd) 38 + if err != nil { 39 + t.Fatalf("WrapMulti: %v", err) 40 + } 41 + 42 + // argv[0] is the selfExe 43 + if wrapped.Path != "/path/to/knot" { 44 + t.Errorf("Path = %q, want %q", wrapped.Path, "/path/to/knot") 45 + } 46 + 47 + // argv should be: [knot, sandbox-exec, --repo-path=/repos/a, --repo-path=/repos/b, --, <abs-git>, upload-pack, ...] 48 + args := wrapped.Args 49 + if len(args) < 6 { 50 + t.Fatalf("Args too short: %v", args) 51 + } 52 + if args[0] != "/path/to/knot" { 53 + t.Errorf("Args[0] = %q, want %q", args[0], "/path/to/knot") 54 + } 55 + if args[1] != "sandbox-exec" { 56 + t.Errorf("Args[1] = %q, want %q", args[1], "sandbox-exec") 57 + } 58 + if args[2] != "--repo-path=/repos/a" { 59 + t.Errorf("Args[2] = %q, want --repo-path=/repos/a", args[2]) 60 + } 61 + if args[3] != "--repo-path=/repos/b" { 62 + t.Errorf("Args[3] = %q, want --repo-path=/repos/b", args[3]) 63 + } 64 + if args[4] != "--" { 65 + t.Errorf("Args[4] = %q, want --", args[4]) 66 + } 67 + // args[5] is the resolved absolute git path; just check it ends with /git 68 + if !strings.HasSuffix(args[5], "/git") && args[5] != "git" { 69 + t.Errorf("Args[5] = %q, want path ending in /git or bare \"git\"", args[5]) 70 + } 71 + if got := args[len(args)-1]; got != "." { 72 + t.Errorf("last arg = %q, want %q", got, ".") 73 + } 74 + 75 + // Dir should be the first repo path so the kernel chdirs there 76 + // after setuid, before execve. 77 + if wrapped.Dir != "/repos/a" { 78 + t.Errorf("Dir = %q, want %q", wrapped.Dir, "/repos/a") 79 + } 80 + 81 + // Env propagated 82 + if len(wrapped.Env) != 1 || wrapped.Env[0] != "GIT_PROTOCOL=version=2" { 83 + t.Errorf("Env = %v, want [GIT_PROTOCOL=version=2]", wrapped.Env) 84 + } 85 + 86 + // Stdio propagated 87 + if wrapped.Stdin != cmd.Stdin { 88 + t.Error("Stdin not propagated to wrapped cmd") 89 + } 90 + } 91 + 92 + func TestLandlockBackend_WrapMulti_NoLookupNoCredential(t *testing.T) { 93 + sb := &LandlockBackend{selfExe: "/path/to/knot"} // no lookup 94 + cmd := exec.Command("git", "status") 95 + 96 + wrapped, err := sb.WrapMulti([]string{"/repos/a"}, cmd) 97 + if err != nil { 98 + t.Fatalf("WrapMulti: %v", err) 99 + } 100 + if wrapped.SysProcAttr != nil && wrapped.SysProcAttr.Credential != nil { 101 + t.Error("no lookup configured; Credential should not be set") 102 + } 103 + } 104 + 105 + func TestLandlockBackend_WrapMulti_LookupSetsCredential(t *testing.T) { 106 + // lookup deliberately returns a different gid (the service group) to 107 + // confirm that WrapMulti ignores it and uses uid as the primary gid. 108 + // see the comment in sandbox_linux.go for why this matters. 109 + sb := &LandlockBackend{ 110 + selfExe: "/path/to/knot", 111 + lookup: func(repoPath string) (uint32, uint32, error) { 112 + if repoPath != "/repos/a" { 113 + t.Errorf("lookup called with %q, want /repos/a", repoPath) 114 + } 115 + return 100042, 1234, nil 116 + }, 117 + } 118 + cmd := exec.Command("git", "status") 119 + 120 + wrapped, err := sb.WrapMulti([]string{"/repos/a"}, cmd) 121 + if err != nil { 122 + t.Fatalf("WrapMulti: %v", err) 123 + } 124 + if wrapped.SysProcAttr == nil || wrapped.SysProcAttr.Credential == nil { 125 + t.Fatal("Credential should be set when lookup returns uid > 0") 126 + } 127 + cred := wrapped.SysProcAttr.Credential 128 + if cred.Uid != 100042 { 129 + t.Errorf("Credential.Uid = %d, want 100042", cred.Uid) 130 + } 131 + if cred.Gid != 100042 { 132 + t.Errorf("Credential.Gid = %d, want 100042 (must equal Uid, not lookup's gid 1234)", cred.Gid) 133 + } 134 + if !cred.NoSetGroups { 135 + t.Error("NoSetGroups should be true") 136 + } 137 + } 138 + 139 + func TestLandlockBackend_WrapMulti_LookupErrSkipsCredential(t *testing.T) { 140 + sb := &LandlockBackend{ 141 + selfExe: "/path/to/knot", 142 + lookup: func(string) (uint32, uint32, error) { 143 + return 0, 0, syscall.ENOENT 144 + }, 145 + } 146 + cmd := exec.Command("git", "status") 147 + 148 + wrapped, err := sb.WrapMulti([]string{"/repos/a"}, cmd) 149 + if err != nil { 150 + t.Fatalf("WrapMulti: %v", err) 151 + } 152 + if wrapped.SysProcAttr != nil && wrapped.SysProcAttr.Credential != nil { 153 + t.Error("lookup errored; Credential should not be set") 154 + } 155 + } 156 + 157 + func TestLandlockBackend_WrapMulti_LookupZeroSkipsCredential(t *testing.T) { 158 + // uid == 0 is treated as "don't drop" so we never accidentally drop to 159 + // root. Verify Credential isn't set in that case. 160 + sb := &LandlockBackend{ 161 + selfExe: "/path/to/knot", 162 + lookup: func(string) (uint32, uint32, error) { 163 + return 0, 0, nil 164 + }, 165 + } 166 + cmd := exec.Command("git", "status") 167 + 168 + wrapped, err := sb.WrapMulti([]string{"/repos/a"}, cmd) 169 + if err != nil { 170 + t.Fatalf("WrapMulti: %v", err) 171 + } 172 + if wrapped.SysProcAttr != nil && wrapped.SysProcAttr.Credential != nil { 173 + t.Error("lookup returned uid=0; Credential should not be set") 174 + } 175 + }
+55
knotserver/sandbox/sandbox_test.go
··· 1 + package sandbox 2 + 3 + import ( 4 + "os/exec" 5 + "testing" 6 + ) 7 + 8 + func TestNoopBackend_Wrap(t *testing.T) { 9 + sb := &NoopBackend{} 10 + cmd := exec.Command("git", "status") 11 + 12 + wrapped, err := sb.Wrap("/some/repo", cmd) 13 + if err != nil { 14 + t.Fatalf("Wrap: %v", err) 15 + } 16 + if wrapped != cmd { 17 + t.Error("Wrap should return the same cmd, not a new one") 18 + } 19 + if wrapped.Dir != "/some/repo" { 20 + t.Errorf("Dir = %q, want %q", wrapped.Dir, "/some/repo") 21 + } 22 + } 23 + 24 + func TestNoopBackend_WrapMulti(t *testing.T) { 25 + sb := &NoopBackend{} 26 + cmd := exec.Command("git", "merge") 27 + 28 + wrapped, err := sb.WrapMulti([]string{"/a", "/b"}, cmd) 29 + if err != nil { 30 + t.Fatalf("WrapMulti: %v", err) 31 + } 32 + if wrapped.Dir != "/a" { 33 + t.Errorf("Dir = %q, want %q (first path)", wrapped.Dir, "/a") 34 + } 35 + } 36 + 37 + func TestNoopBackend_WrapMulti_Empty(t *testing.T) { 38 + sb := &NoopBackend{} 39 + cmd := exec.Command("git", "status") 40 + cmd.Dir = "/preserved" 41 + 42 + wrapped, err := sb.WrapMulti(nil, cmd) 43 + if err != nil { 44 + t.Fatalf("WrapMulti: %v", err) 45 + } 46 + if wrapped.Dir != "/preserved" { 47 + t.Errorf("empty paths should not overwrite cmd.Dir; got %q", wrapped.Dir) 48 + } 49 + } 50 + 51 + func TestNoopBackend_Name(t *testing.T) { 52 + if (&NoopBackend{}).Name() != "noop" { 53 + t.Error("Name should return \"noop\"") 54 + } 55 + }