carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "bytes"
7 "errors"
8 "log"
9 "net"
10 "os"
11 "path/filepath"
12 "strings"
13 "testing"
14
15 "golang.org/x/sys/unix"
16)
17
18func TestBootstrapRegistryPlantedLinks(t *testing.T) {
19 for _, dangling := range []bool{false, true} {
20 t.Run(map[bool]string{false: "existing", true: "dangling"}[dangling], func(t *testing.T) {
21 root := t.TempDir()
22 victim := filepath.Join(t.TempDir(), "victim")
23 if !dangling {
24 if err := os.WriteFile(victim, []byte("preserve"), 0o600); err != nil {
25 t.Fatal(err)
26 }
27 }
28 if err := os.Mkdir(filepath.Join(root, "data"), 0o750); err != nil {
29 t.Fatal(err)
30 }
31 if err := os.Symlink(victim, filepath.Join(root, "data", "registry.db")); err != nil {
32 t.Fatal(err)
33 }
34 if err := bootstrap(root, nil); err != nil {
35 t.Fatal(err)
36 }
37 got, err := os.ReadFile(victim)
38 if dangling {
39 if !errors.Is(err, os.ErrNotExist) {
40 t.Fatalf("dangling target created: %v", err)
41 }
42 } else if err != nil || string(got) != "preserve" {
43 t.Fatalf("victim changed: %q %v", got, err)
44 }
45 })
46 }
47}
48
49func TestBootstrapRefusesWritableParentSymlink(t *testing.T) {
50 for _, parent := range []string{"data", "data/.instances", "run/carlos"} {
51 t.Run(strings.ReplaceAll(parent, "/", "_"), func(t *testing.T) {
52 root, victim := t.TempDir(), t.TempDir()
53 if err := os.Chmod(victim, 0o711); err != nil {
54 t.Fatal(err)
55 }
56 path := filepath.Join(root, parent)
57 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
58 t.Fatal(err)
59 }
60 if err := os.Symlink(victim, path); err != nil {
61 t.Fatal(err)
62 }
63 if err := bootstrap(root, nil); err == nil {
64 t.Fatal("accepted parent symlink")
65 }
66 st, err := os.Stat(victim)
67 if err != nil || st.Mode().Perm() != 0o711 {
68 t.Fatalf("victim mode changed: %v %v", st, err)
69 }
70 entries, err := os.ReadDir(victim)
71 if err != nil || len(entries) != 0 {
72 t.Fatalf("victim populated: %v %v", entries, err)
73 }
74 })
75 }
76}
77
78func TestBootstrapOwnershipWalkSkipsUnsafeEntries(t *testing.T) {
79 dir := t.TempDir()
80 victim := filepath.Join(t.TempDir(), "victim")
81 if err := os.WriteFile(victim, []byte("preserve"), 0o600); err != nil {
82 t.Fatal(err)
83 }
84 if err := os.Chmod(victim, 0o750|os.ModeSetuid|os.ModeSetgid); err != nil {
85 t.Fatal(err)
86 }
87 before, err := os.Stat(victim)
88 if err != nil {
89 t.Fatal(err)
90 }
91 for _, name := range []string{"registry.db-wal", "registry.db-shm"} {
92 if err := os.Link(victim, filepath.Join(dir, name)); err != nil {
93 t.Fatal(err)
94 }
95 }
96 if err := os.Symlink(victim, filepath.Join(dir, "registry.db")); err != nil {
97 t.Fatal(err)
98 }
99 if err := unix.Mkfifo(filepath.Join(dir, "fifo"), 0o600); err != nil {
100 t.Fatal(err)
101 }
102 socket, err := net.Listen("unix", filepath.Join(dir, "socket"))
103 if err != nil {
104 t.Fatal(err)
105 }
106 defer socket.Close()
107 if err := os.Mkdir(filepath.Join(dir, "nested"), 0o750); err != nil {
108 t.Fatal(err)
109 }
110 plain := filepath.Join(dir, "nested", "plain")
111 if err := os.WriteFile(plain, []byte("ordinary"), 0o640); err != nil {
112 t.Fatal(err)
113 }
114 var logs bytes.Buffer
115 old := log.Writer()
116 log.SetOutput(&logs)
117 defer log.SetOutput(old)
118 if err := bootstrapChownTree(dir, os.Getuid(), os.Getgid()); err != nil {
119 t.Fatal(err)
120 }
121 after, err := os.Stat(victim)
122 if err != nil {
123 t.Fatal(err)
124 }
125 if after.Mode() != before.Mode() {
126 t.Fatalf("hard-linked victim ownership operation cleared mode: %v -> %v", before.Mode(), after.Mode())
127 }
128 got, err := os.ReadFile(victim)
129 if err != nil || string(got) != "preserve" {
130 t.Fatalf("victim changed: %q %v", got, err)
131 }
132 for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "fifo", "socket"} {
133 if !strings.Contains(logs.String(), filepath.Join(dir, name)) {
134 t.Errorf("missing alert for %s: %s", name, logs.String())
135 }
136 }
137 got, err = os.ReadFile(plain)
138 if err != nil || string(got) != "ordinary" {
139 t.Fatalf("plain file changed: %q %v", got, err)
140 }
141}
142
143func TestBootstrapOwnershipWalkDoesNotCrossBindMount(t *testing.T) {
144 if os.Geteuid() != 0 {
145 t.Skip("bind mount test requires root with CAP_SYS_ADMIN")
146 }
147 dir, source := t.TempDir(), t.TempDir()
148 target := filepath.Join(dir, "bind")
149 if err := os.Mkdir(target, 0o750); err != nil {
150 t.Fatal(err)
151 }
152 if err := os.Chmod(source, 0o750|os.ModeSetgid); err != nil {
153 t.Fatal(err)
154 }
155 if err := unix.Mount(source, target, "", unix.MS_BIND, ""); err != nil {
156 if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) {
157 t.Skipf("bind mount test requires CAP_SYS_ADMIN: %v", err)
158 }
159 t.Fatal(err)
160 }
161 defer unix.Unmount(target, 0)
162 before, err := os.Stat(source)
163 if err != nil {
164 t.Fatal(err)
165 }
166 if err := bootstrapChownTree(dir, 65534, 65534); err != nil {
167 t.Fatal(err)
168 }
169 after, err := os.Stat(source)
170 if err != nil {
171 t.Fatal(err)
172 }
173 var sourceStat unix.Stat_t
174 if err := unix.Stat(source, &sourceStat); err != nil {
175 t.Fatal(err)
176 }
177 if before.Mode() != after.Mode() || sourceStat.Uid != uint32(os.Getuid()) {
178 t.Fatalf("crossed bind mount: mode %v -> %v, uid %d", before.Mode(), after.Mode(), sourceStat.Uid)
179 }
180}
181