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 cgroup
4
5import (
6 "context"
7 "errors"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "sync/atomic"
12 "syscall"
13 "testing"
14 "time"
15
16 "golang.org/x/sys/unix"
17)
18
19// Filesystem fixtures test handle state only. PrepareStrict must reject them;
20// the delegated-kernel suite separately proves actual containment and controls.
21func strictFixture(t *testing.T) *Prepared {
22 t.Helper()
23 forgetKillsAfter(t)
24 root := t.TempDir()
25 host := "fixture.example"
26 dir := filepath.Join(root, ExecName(host))
27 if e := os.Mkdir(dir, 0700); e != nil {
28 t.Fatal(e)
29 }
30 for name, value := range map[string]string{"cgroup.events": "populated 0\nfrozen 0\n", "cgroup.kill": "", "memory.max": "4096", "memory.high": "4096", "memory.swap.max": "4096", "memory.oom.group": "1"} {
31 if e := os.WriteFile(filepath.Join(dir, name), []byte(value), 0600); e != nil {
32 t.Fatal(e)
33 }
34 }
35 open := func(path string) *os.File {
36 flags := os.O_RDWR
37 if info, e := os.Stat(path); e == nil && info.IsDir() {
38 flags = os.O_RDONLY
39 }
40 f, e := os.OpenFile(path, flags, 0)
41 if e != nil {
42 t.Fatal(e)
43 }
44 return f
45 }
46 p := &Prepared{strictPreparedState: &strictPreparedState{root: open(root), dir: open(dir), host: host, limits: map[string]*os.File{}, expected: map[string]string{}, start: func(*exec.Cmd) error { return syscall.ENOSYS }}}
47 p.kill = open(filepath.Join(dir, "cgroup.kill"))
48 p.events = open(filepath.Join(dir, "cgroup.events"))
49 p.identity, _ = strictID(p.dir)
50 for _, name := range []string{"memory.max", "memory.high", "memory.swap.max", "memory.oom.group"} {
51 p.limits[name] = open(filepath.Join(dir, name))
52 p.expected[name], _ = strictRead(p.limits[name])
53 }
54 strictReservations.Lock()
55 strictReservations.held[p.identity] = true
56 strictReservations.Unlock()
57 t.Cleanup(func() {
58 p.closeFiles()
59 strictReservations.Lock()
60 delete(strictReservations.held, p.identity)
61 strictReservations.Unlock()
62 })
63 return p
64}
65func strictContext(t *testing.T, d time.Duration) context.Context {
66 t.Helper()
67 ctx, cancel := context.WithTimeout(t.Context(), d)
68 t.Cleanup(cancel)
69 return ctx
70}
71
72func TestStrictPopulationUsesRetainedIdentity(t *testing.T) {
73 p := strictFixture(t)
74 for _, populated := range []bool{false, true} {
75 value := "populated 0\nfrozen 0\n"
76 if populated {
77 value = "populated 1\nfrozen 0\n"
78 }
79 if e := strictWrite(p.events, value); e != nil {
80 t.Fatal(e)
81 }
82 if got, e := p.Populated(); e != nil || got != populated {
83 t.Fatal("incorrect population", got, e)
84 }
85 }
86 if e := os.Rename(p.dir.Name(), p.dir.Name()+".old"); e != nil {
87 t.Fatal(e)
88 }
89 if _, e := p.Populated(); e == nil {
90 t.Fatal("replaced cgroup accepted")
91 }
92 if _, e := (&Prepared{}).Populated(); e == nil {
93 t.Fatal("empty handle accepted")
94 }
95}
96func TestStrictRejectsUnavailableManagerHostAndTier(t *testing.T) {
97 for _, m := range []*Manager{nil, {root: t.TempDir()}} {
98 for _, host := range []string{"ok.example", "../outside", "Upper.example", "", "a/b"} {
99 if p, e := m.PrepareStrict(host, TierDefault); e == nil || p != nil {
100 t.Fatal("unsupported filesystem or host accepted")
101 }
102 }
103 }
104 m := &Manager{root: "/sys/fs/cgroup"}
105 for _, limit := range []Limit{{}, {MemoryMax: -1}, {MemoryMax: 1}} {
106 if p, e := m.PrepareStrict("x.example", limit); e == nil || p != nil {
107 t.Fatal("missing or foreign tier accepted")
108 }
109 }
110}
111func TestStrictStartConsumesOneAttemptWithoutFallback(t *testing.T) {
112 p := strictFixture(t)
113 var calls atomic.Int32
114 p.start = func(cmd *exec.Cmd) error {
115 calls.Add(1)
116 if cmd.SysProcAttr == nil || !cmd.SysProcAttr.UseCgroupFD || cmd.SysProcAttr.CgroupFD != int(p.dir.Fd()) || cmd.SysProcAttr.Pdeathsig != syscall.SIGKILL || !cmd.SysProcAttr.Setpgid {
117 t.Error("strict spawn attributes missing or clobbered")
118 }
119 return syscall.ENOSYS
120 }
121 copied := *p
122 cmd := exec.Command("/not-executed")
123 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
124 if e := p.Start(cmd); !errors.Is(e, ErrStrictUnsupported) {
125 t.Fatal("unsupported clone accepted", e)
126 }
127 if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) {
128 t.Fatal("second spawn accepted", e)
129 }
130 if e := copied.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) {
131 t.Fatal("copied handle restored spawn authority", e)
132 }
133 if calls.Load() != 1 {
134 t.Fatal("strict start retried", calls.Load())
135 }
136 if raw, e := strictRead(p.kill); e != nil || raw != "" {
137 t.Fatal("failed spawn invented cleanup", raw, e)
138 }
139}
140func TestStrictCleanupRequiresDeadlineAndSurvivesTimeout(t *testing.T) {
141 p := strictFixture(t)
142 for _, ctx := range []context.Context{nil, context.Background(), strictContext(t, -time.Second)} {
143 if e := p.KillAndEmpty(ctx); !errors.Is(e, ErrStrictDeadline) {
144 t.Fatal("unbounded/expired cleanup accepted", e)
145 }
146 }
147 if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), "cgroup.events"), []byte("populated 1\n"), 0600); e != nil {
148 t.Fatal(e)
149 }
150 if e := p.Close(); !errors.Is(e, ErrStrictRefused) {
151 t.Fatal("populated group handle discarded", e)
152 }
153 if e := p.KillAndEmpty(strictContext(t, 15*time.Millisecond)); !errors.Is(e, ErrStrictDeadline) {
154 t.Fatal("populated group claimed empty", e)
155 }
156 if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) {
157 t.Fatal("cleanup timeout restored spawn authority", e)
158 }
159 if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), "cgroup.events"), []byte("populated 0\n"), 0600); e != nil {
160 t.Fatal(e)
161 }
162 if e := p.KillAndEmpty(strictContext(t, time.Second)); e != nil {
163 t.Fatal("same-handle cleanup retry failed", e)
164 }
165 if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictState) {
166 t.Fatal("second cleanup reused proof", e)
167 }
168 if e := p.Close(); e != nil {
169 t.Fatal(e)
170 }
171 if e := p.Close(); !errors.Is(e, ErrStrictState) {
172 t.Fatal("double close accepted")
173 }
174 if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) {
175 t.Fatal("closed spawn accepted")
176 }
177 if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictState) {
178 t.Fatal("closed cleanup accepted")
179 }
180}
181func TestStrictCleanupMutexWaitHonorsDeadline(t *testing.T) {
182 p := strictFixture(t)
183 p.mu.Lock()
184 defer p.mu.Unlock()
185 start := time.Now()
186 if e := p.KillAndEmpty(strictContext(t, 15*time.Millisecond)); !errors.Is(e, ErrStrictDeadline) {
187 t.Fatal(e)
188 }
189 if time.Since(start) > 250*time.Millisecond {
190 t.Fatal("mutex wait escaped caller deadline")
191 }
192}
193func TestStrictStaleHandleCannotKillReplacement(t *testing.T) {
194 p := strictFixture(t)
195 old := filepath.Join(p.root.Name(), ExecName(p.host))
196 if e := os.Rename(old, old+"-old"); e != nil {
197 t.Fatal(e)
198 }
199 if e := os.Mkdir(old, 0700); e != nil {
200 t.Fatal(e)
201 }
202 kill := filepath.Join(old, "cgroup.kill")
203 if e := os.WriteFile(kill, []byte("replacement"), 0600); e != nil {
204 t.Fatal(e)
205 }
206 if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictRefused) {
207 t.Fatal("wrong identity accepted", e)
208 }
209 if e := p.Close(); !errors.Is(e, ErrStrictRefused) {
210 t.Fatal("stale close claimed proof", e)
211 }
212 raw, e := os.ReadFile(kill)
213 if e != nil || string(raw) != "replacement" {
214 t.Fatal("replacement group touched", e)
215 }
216 if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) {
217 t.Fatal("stale spawn accepted", e)
218 }
219}
220func TestStrictChangedLimitsAndMalformedEventsRefuse(t *testing.T) {
221 for _, name := range []string{"memory.max", "memory.high", "memory.swap.max", "memory.oom.group", "cgroup.events"} {
222 t.Run(name, func(t *testing.T) {
223 p := strictFixture(t)
224 if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), name), []byte("missing\n"), 0600); e != nil {
225 t.Fatal(e)
226 }
227 called := false
228 p.start = func(*exec.Cmd) error { called = true; return nil }
229 if e := p.Start(exec.Command("/not-executed")); e == nil || called {
230 t.Fatal("unchecked controls reached spawn", e)
231 }
232 if name == "cgroup.events" {
233 if e := p.Close(); e == nil {
234 t.Fatal("unknown emptiness discarded handle")
235 }
236 }
237 })
238 }
239}
240
241func TestStrictConcurrentAndInvalidStartsConsumeAttempt(t *testing.T) {
242 p := strictFixture(t)
243 var calls atomic.Int32
244 p.start = func(*exec.Cmd) error { calls.Add(1); return syscall.ENOSYS }
245 results := make(chan error, 16)
246 for range 16 {
247 go func() { results <- p.Start(exec.Command("/not-executed")) }()
248 }
249 unsupported := 0
250 for range 16 {
251 switch err := <-results; err {
252 case ErrStrictUnsupported:
253 unsupported++
254 case ErrStrictState:
255 default:
256 t.Fatal(err)
257 }
258 }
259 if calls.Load() != 1 || unsupported != 1 {
260 t.Fatal("concurrent starts repeated attempt")
261 }
262 q := strictFixture(t)
263 if err := q.Start(nil); err != ErrStrictState {
264 t.Fatal(err)
265 }
266 if err := q.Start(exec.Command("/not-executed")); err != ErrStrictState {
267 t.Fatal("invalid command restored attempt", err)
268 }
269 for _, zero := range []*Prepared{nil, {}} {
270 if zero.Start(nil) != ErrStrictState || zero.Close() != ErrStrictState || zero.KillAndEmpty(context.Background()) != ErrStrictState {
271 t.Fatal("zero handle accepted")
272 }
273 }
274}
275
276// fakeSelfCgroup points the strict preflight's /proc/self/cgroup read at a
277// file holding line, so a test can place the agent wherever it needs.
278func fakeSelfCgroup(t *testing.T, line string) {
279 t.Helper()
280 path := filepath.Join(t.TempDir(), "cgroup")
281 if e := os.WriteFile(path, []byte(line+"\n"), 0o644); e != nil {
282 t.Fatal(e)
283 }
284 orig := selfCgroupFile
285 selfCgroupFile = path
286 t.Cleanup(func() { selfCgroupFile = orig })
287}
288
289// TestStrictPreflightIsPrepareStrictsRuntimeCheck: StrictPreflight answers
290// what PrepareStrict's host-independent runtime checks answer, because it
291// is the same function. Delegation present but the agent placed outside
292// the delegated root or its supervise child is refused by both; placed in
293// either, the preflight passes on a real cgroup2 mount.
294func TestStrictPreflightIsPrepareStrictsRuntimeCheck(t *testing.T) {
295 var nilManager *Manager
296 if e := nilManager.StrictPreflight(); !errors.Is(e, ErrStrictUnsupported) {
297 t.Fatalf("nil manager: %v", e)
298 }
299 var fs unix.Statfs_t
300 if e := unix.Statfs("/sys/fs/cgroup", &fs); e != nil || uint64(fs.Type) != uint64(unix.CGROUP2_SUPER_MAGIC) {
301 t.Skip("no cgroup2 mount at /sys/fs/cgroup")
302 }
303 m := &Manager{root: "/sys/fs/cgroup/carlos.slice/carlos-edge.service"}
304 fakeSelfCgroup(t, "0::/user.slice/elsewhere.service")
305 if e := m.StrictPreflight(); !errors.Is(e, ErrStrictRefused) {
306 t.Fatalf("misplaced agent: preflight %v, want ErrStrictRefused", e)
307 }
308 if p, e := m.PrepareStrict("x.example", TierDefault); p != nil || !errors.Is(e, ErrStrictRefused) {
309 t.Fatalf("misplaced agent: PrepareStrict %v, want the preflight's ErrStrictRefused", e)
310 }
311 // The test's own cgroup stands in for the delegated root: a real
312 // cgroup2 directory the agent is placed in, or in its supervise child.
313 raw, e := os.ReadFile("/proc/self/cgroup")
314 rel, ok := ParseSelfCgroup(string(raw))
315 if e != nil || !ok {
316 t.Skip("no cgroup v2 placement for this test process")
317 }
318 own := &Manager{root: filepath.Join("/sys/fs/cgroup", rel)}
319 for _, line := range []string{"0::/" + rel, "0::/" + rel + "/supervise"} {
320 fakeSelfCgroup(t, line)
321 if e := own.StrictPreflight(); e != nil {
322 t.Fatalf("agent at %q: preflight %v, want nil", line, e)
323 }
324 }
325 fakeSelfCgroup(t, "0::/"+rel)
326 if e := (&Manager{root: t.TempDir()}).StrictPreflight(); !errors.Is(e, ErrStrictRefused) {
327 t.Fatalf("root outside the agent's placement: %v", e)
328 }
329}
330