This repository has no description
5.4 kB
193 lines
1package sandbox
2
3import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "testing"
8)
9
10func 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 wantPerm os.FileMode
33 wantDir bool
34 }{
35 {root, 0770, true},
36 {filepath.Join(root, "file.txt"), 0660, false},
37 {filepath.Join(root, "script.sh"), 0770, false},
38 {filepath.Join(root, "subdir"), 0770, true},
39 {filepath.Join(root, "subdir", "nested.txt"), 0660, false},
40 }
41 for _, c := range cases {
42 info, err := os.Stat(c.path)
43 if err != nil {
44 t.Errorf("stat %s: %v", c.path, err)
45 continue
46 }
47 if got := info.Mode().Perm(); got != c.wantPerm {
48 t.Errorf("%s: perm = %o, want %o", c.path, got, c.wantPerm)
49 }
50 setgid := info.Mode()&os.ModeSetgid != 0
51 if c.wantDir && !setgid {
52 t.Errorf("%s: setgid bit not set on directory", c.path)
53 }
54 if !c.wantDir && setgid {
55 t.Errorf("%s: setgid bit set on non-directory", c.path)
56 }
57 }
58}
59
60func TestChmodRepoTree_SetsSetgidOnExistingDirs(t *testing.T) {
61 // Directories that already exist without the setgid bit should have it
62 // applied by the chmod walk; otherwise, files later created inside them
63 // by sandbox subprocesses would not inherit the directory's group.
64 root := t.TempDir()
65 mustMkdir(t, filepath.Join(root, "objects"), 0755)
66 mustMkdir(t, filepath.Join(root, "refs", "heads"), 0755)
67
68 if err := ChmodRepoTree(root); err != nil {
69 t.Fatalf("ChmodRepoTree: %v", err)
70 }
71
72 for _, p := range []string{root, filepath.Join(root, "objects"), filepath.Join(root, "refs"), filepath.Join(root, "refs", "heads")} {
73 info, err := os.Stat(p)
74 if err != nil {
75 t.Fatalf("stat %s: %v", p, err)
76 }
77 if info.Mode()&os.ModeSetgid == 0 {
78 t.Errorf("%s: setgid bit not set", p)
79 }
80 }
81}
82
83func TestChmodRepoTree_PreservesExecutableBit(t *testing.T) {
84 root := t.TempDir()
85 mustWrite(t, filepath.Join(root, "exec"), 0744, "")
86 mustWrite(t, filepath.Join(root, "noexec"), 0644, "")
87
88 if err := ChmodRepoTree(root); err != nil {
89 t.Fatalf("ChmodRepoTree: %v", err)
90 }
91
92 if got := mode(t, filepath.Join(root, "exec")); got != 0770 {
93 t.Errorf("exec file: mode = %o, want 0770", got)
94 }
95 if got := mode(t, filepath.Join(root, "noexec")); got != 0660 {
96 t.Errorf("noexec file: mode = %o, want 0660", got)
97 }
98}
99
100func TestChownRepoTree_SelfChown(t *testing.T) {
101 // Chowning to our own UID/GID is always a no-op success. This verifies
102 // the walk visits all entries without erroring.
103 root := t.TempDir()
104 mustWrite(t, filepath.Join(root, "a"), 0644, "")
105 mustMkdir(t, filepath.Join(root, "b"), 0755)
106 mustWrite(t, filepath.Join(root, "b", "c"), 0644, "")
107
108 uid := os.Getuid()
109 gid := os.Getgid()
110 if err := ChownRepoTree(root, uid, gid); err != nil {
111 t.Fatalf("ChownRepoTree: %v", err)
112 }
113
114 // verify everything still belongs to us.
115 for _, p := range []string{root, filepath.Join(root, "a"), filepath.Join(root, "b"), filepath.Join(root, "b", "c")} {
116 info, err := os.Stat(p)
117 if err != nil {
118 t.Fatalf("stat %s: %v", p, err)
119 }
120 _ = info
121 }
122}
123
124func TestLookupUIDForRepoPath(t *testing.T) {
125 if runtime.GOOS == "windows" {
126 t.Skip("uid/gid lookup is unix-only")
127 }
128 scan := t.TempDir()
129 repo := filepath.Join(scan, "did:plc:abc")
130 mustMkdir(t, repo, 0700)
131
132 uid, gid, err := LookupUIDForRepoPath(scan, repo)
133 if err != nil {
134 t.Fatalf("LookupUIDForRepoPath: %v", err)
135 }
136 if uid != uint32(os.Getuid()) {
137 t.Errorf("uid = %d, want %d", uid, os.Getuid())
138 }
139 if gid != uint32(os.Getgid()) {
140 t.Errorf("gid = %d, want %d", gid, os.Getgid())
141 }
142}
143
144func TestLookupUIDForRepoPath_OutsideScanPath(t *testing.T) {
145 _, _, err := LookupUIDForRepoPath("/home/git", "/etc/passwd")
146 if err == nil {
147 t.Fatal("expected error for path outside scan path, got nil")
148 }
149}
150
151func TestLookupUIDForRepoPath_NonexistentPath(t *testing.T) {
152 scan := t.TempDir()
153 _, _, err := LookupUIDForRepoPath(scan, filepath.Join(scan, "does-not-exist"))
154 if err == nil {
155 t.Fatal("expected error for nonexistent path, got nil")
156 }
157}
158
159// helpers
160
161func mustWrite(t *testing.T, path string, mode os.FileMode, content string) {
162 t.Helper()
163 if err := os.WriteFile(path, []byte(content), mode); err != nil {
164 t.Fatalf("write %s: %v", path, err)
165 }
166 // WriteFile respects existing mode on overwrite; chmod to be sure.
167 if err := os.Chmod(path, mode); err != nil {
168 t.Fatalf("chmod %s: %v", path, err)
169 }
170}
171
172func mustMkdir(t *testing.T, path string, mode os.FileMode) {
173 t.Helper()
174 if err := os.MkdirAll(path, mode); err != nil {
175 t.Fatalf("mkdir %s: %v", path, err)
176 }
177}
178
179func mustSymlink(t *testing.T, target, link string) {
180 t.Helper()
181 if err := os.Symlink(target, link); err != nil {
182 t.Fatalf("symlink %s -> %s: %v", link, target, err)
183 }
184}
185
186func mode(t *testing.T, path string) os.FileMode {
187 t.Helper()
188 info, err := os.Stat(path)
189 if err != nil {
190 t.Fatalf("stat %s: %v", path, err)
191 }
192 return info.Mode().Perm()
193}