//go:build linux package cgroup import ( "context" "errors" "os" "os/exec" "path/filepath" "sync/atomic" "syscall" "testing" "time" "golang.org/x/sys/unix" ) // Filesystem fixtures test handle state only. PrepareStrict must reject them; // the delegated-kernel suite separately proves actual containment and controls. func strictFixture(t *testing.T) *Prepared { t.Helper() forgetKillsAfter(t) root := t.TempDir() host := "fixture.example" dir := filepath.Join(root, ExecName(host)) if e := os.Mkdir(dir, 0700); e != nil { t.Fatal(e) } 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"} { if e := os.WriteFile(filepath.Join(dir, name), []byte(value), 0600); e != nil { t.Fatal(e) } } open := func(path string) *os.File { flags := os.O_RDWR if info, e := os.Stat(path); e == nil && info.IsDir() { flags = os.O_RDONLY } f, e := os.OpenFile(path, flags, 0) if e != nil { t.Fatal(e) } return f } 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 }}} p.kill = open(filepath.Join(dir, "cgroup.kill")) p.events = open(filepath.Join(dir, "cgroup.events")) p.identity, _ = strictID(p.dir) for _, name := range []string{"memory.max", "memory.high", "memory.swap.max", "memory.oom.group"} { p.limits[name] = open(filepath.Join(dir, name)) p.expected[name], _ = strictRead(p.limits[name]) } strictReservations.Lock() strictReservations.held[p.identity] = true strictReservations.Unlock() t.Cleanup(func() { p.closeFiles() strictReservations.Lock() delete(strictReservations.held, p.identity) strictReservations.Unlock() }) return p } func strictContext(t *testing.T, d time.Duration) context.Context { t.Helper() ctx, cancel := context.WithTimeout(t.Context(), d) t.Cleanup(cancel) return ctx } func TestStrictPopulationUsesRetainedIdentity(t *testing.T) { p := strictFixture(t) for _, populated := range []bool{false, true} { value := "populated 0\nfrozen 0\n" if populated { value = "populated 1\nfrozen 0\n" } if e := strictWrite(p.events, value); e != nil { t.Fatal(e) } if got, e := p.Populated(); e != nil || got != populated { t.Fatal("incorrect population", got, e) } } if e := os.Rename(p.dir.Name(), p.dir.Name()+".old"); e != nil { t.Fatal(e) } if _, e := p.Populated(); e == nil { t.Fatal("replaced cgroup accepted") } if _, e := (&Prepared{}).Populated(); e == nil { t.Fatal("empty handle accepted") } } func TestStrictRejectsUnavailableManagerHostAndTier(t *testing.T) { for _, m := range []*Manager{nil, {root: t.TempDir()}} { for _, host := range []string{"ok.example", "../outside", "Upper.example", "", "a/b"} { if p, e := m.PrepareStrict(host, TierDefault); e == nil || p != nil { t.Fatal("unsupported filesystem or host accepted") } } } m := &Manager{root: "/sys/fs/cgroup"} for _, limit := range []Limit{{}, {MemoryMax: -1}, {MemoryMax: 1}} { if p, e := m.PrepareStrict("x.example", limit); e == nil || p != nil { t.Fatal("missing or foreign tier accepted") } } } func TestStrictStartConsumesOneAttemptWithoutFallback(t *testing.T) { p := strictFixture(t) var calls atomic.Int32 p.start = func(cmd *exec.Cmd) error { calls.Add(1) if cmd.SysProcAttr == nil || !cmd.SysProcAttr.UseCgroupFD || cmd.SysProcAttr.CgroupFD != int(p.dir.Fd()) || cmd.SysProcAttr.Pdeathsig != syscall.SIGKILL || !cmd.SysProcAttr.Setpgid { t.Error("strict spawn attributes missing or clobbered") } return syscall.ENOSYS } copied := *p cmd := exec.Command("/not-executed") cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if e := p.Start(cmd); !errors.Is(e, ErrStrictUnsupported) { t.Fatal("unsupported clone accepted", e) } if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) { t.Fatal("second spawn accepted", e) } if e := copied.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) { t.Fatal("copied handle restored spawn authority", e) } if calls.Load() != 1 { t.Fatal("strict start retried", calls.Load()) } if raw, e := strictRead(p.kill); e != nil || raw != "" { t.Fatal("failed spawn invented cleanup", raw, e) } } func TestStrictCleanupRequiresDeadlineAndSurvivesTimeout(t *testing.T) { p := strictFixture(t) for _, ctx := range []context.Context{nil, context.Background(), strictContext(t, -time.Second)} { if e := p.KillAndEmpty(ctx); !errors.Is(e, ErrStrictDeadline) { t.Fatal("unbounded/expired cleanup accepted", e) } } if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), "cgroup.events"), []byte("populated 1\n"), 0600); e != nil { t.Fatal(e) } if e := p.Close(); !errors.Is(e, ErrStrictRefused) { t.Fatal("populated group handle discarded", e) } if e := p.KillAndEmpty(strictContext(t, 15*time.Millisecond)); !errors.Is(e, ErrStrictDeadline) { t.Fatal("populated group claimed empty", e) } if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) { t.Fatal("cleanup timeout restored spawn authority", e) } if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), "cgroup.events"), []byte("populated 0\n"), 0600); e != nil { t.Fatal(e) } if e := p.KillAndEmpty(strictContext(t, time.Second)); e != nil { t.Fatal("same-handle cleanup retry failed", e) } if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictState) { t.Fatal("second cleanup reused proof", e) } if e := p.Close(); e != nil { t.Fatal(e) } if e := p.Close(); !errors.Is(e, ErrStrictState) { t.Fatal("double close accepted") } if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) { t.Fatal("closed spawn accepted") } if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictState) { t.Fatal("closed cleanup accepted") } } func TestStrictCleanupMutexWaitHonorsDeadline(t *testing.T) { p := strictFixture(t) p.mu.Lock() defer p.mu.Unlock() start := time.Now() if e := p.KillAndEmpty(strictContext(t, 15*time.Millisecond)); !errors.Is(e, ErrStrictDeadline) { t.Fatal(e) } if time.Since(start) > 250*time.Millisecond { t.Fatal("mutex wait escaped caller deadline") } } func TestStrictStaleHandleCannotKillReplacement(t *testing.T) { p := strictFixture(t) old := filepath.Join(p.root.Name(), ExecName(p.host)) if e := os.Rename(old, old+"-old"); e != nil { t.Fatal(e) } if e := os.Mkdir(old, 0700); e != nil { t.Fatal(e) } kill := filepath.Join(old, "cgroup.kill") if e := os.WriteFile(kill, []byte("replacement"), 0600); e != nil { t.Fatal(e) } if e := p.KillAndEmpty(strictContext(t, time.Second)); !errors.Is(e, ErrStrictRefused) { t.Fatal("wrong identity accepted", e) } if e := p.Close(); !errors.Is(e, ErrStrictRefused) { t.Fatal("stale close claimed proof", e) } raw, e := os.ReadFile(kill) if e != nil || string(raw) != "replacement" { t.Fatal("replacement group touched", e) } if e := p.Start(exec.Command("/not-executed")); !errors.Is(e, ErrStrictState) { t.Fatal("stale spawn accepted", e) } } func TestStrictChangedLimitsAndMalformedEventsRefuse(t *testing.T) { for _, name := range []string{"memory.max", "memory.high", "memory.swap.max", "memory.oom.group", "cgroup.events"} { t.Run(name, func(t *testing.T) { p := strictFixture(t) if e := os.WriteFile(filepath.Join(p.root.Name(), ExecName(p.host), name), []byte("missing\n"), 0600); e != nil { t.Fatal(e) } called := false p.start = func(*exec.Cmd) error { called = true; return nil } if e := p.Start(exec.Command("/not-executed")); e == nil || called { t.Fatal("unchecked controls reached spawn", e) } if name == "cgroup.events" { if e := p.Close(); e == nil { t.Fatal("unknown emptiness discarded handle") } } }) } } func TestStrictConcurrentAndInvalidStartsConsumeAttempt(t *testing.T) { p := strictFixture(t) var calls atomic.Int32 p.start = func(*exec.Cmd) error { calls.Add(1); return syscall.ENOSYS } results := make(chan error, 16) for range 16 { go func() { results <- p.Start(exec.Command("/not-executed")) }() } unsupported := 0 for range 16 { switch err := <-results; err { case ErrStrictUnsupported: unsupported++ case ErrStrictState: default: t.Fatal(err) } } if calls.Load() != 1 || unsupported != 1 { t.Fatal("concurrent starts repeated attempt") } q := strictFixture(t) if err := q.Start(nil); err != ErrStrictState { t.Fatal(err) } if err := q.Start(exec.Command("/not-executed")); err != ErrStrictState { t.Fatal("invalid command restored attempt", err) } for _, zero := range []*Prepared{nil, {}} { if zero.Start(nil) != ErrStrictState || zero.Close() != ErrStrictState || zero.KillAndEmpty(context.Background()) != ErrStrictState { t.Fatal("zero handle accepted") } } } // fakeSelfCgroup points the strict preflight's /proc/self/cgroup read at a // file holding line, so a test can place the agent wherever it needs. func fakeSelfCgroup(t *testing.T, line string) { t.Helper() path := filepath.Join(t.TempDir(), "cgroup") if e := os.WriteFile(path, []byte(line+"\n"), 0o644); e != nil { t.Fatal(e) } orig := selfCgroupFile selfCgroupFile = path t.Cleanup(func() { selfCgroupFile = orig }) } // TestStrictPreflightIsPrepareStrictsRuntimeCheck: StrictPreflight answers // what PrepareStrict's host-independent runtime checks answer, because it // is the same function. Delegation present but the agent placed outside // the delegated root or its supervise child is refused by both; placed in // either, the preflight passes on a real cgroup2 mount. func TestStrictPreflightIsPrepareStrictsRuntimeCheck(t *testing.T) { var nilManager *Manager if e := nilManager.StrictPreflight(); !errors.Is(e, ErrStrictUnsupported) { t.Fatalf("nil manager: %v", e) } var fs unix.Statfs_t if e := unix.Statfs("/sys/fs/cgroup", &fs); e != nil || uint64(fs.Type) != uint64(unix.CGROUP2_SUPER_MAGIC) { t.Skip("no cgroup2 mount at /sys/fs/cgroup") } m := &Manager{root: "/sys/fs/cgroup/carlos.slice/carlos-edge.service"} fakeSelfCgroup(t, "0::/user.slice/elsewhere.service") if e := m.StrictPreflight(); !errors.Is(e, ErrStrictRefused) { t.Fatalf("misplaced agent: preflight %v, want ErrStrictRefused", e) } if p, e := m.PrepareStrict("x.example", TierDefault); p != nil || !errors.Is(e, ErrStrictRefused) { t.Fatalf("misplaced agent: PrepareStrict %v, want the preflight's ErrStrictRefused", e) } // The test's own cgroup stands in for the delegated root: a real // cgroup2 directory the agent is placed in, or in its supervise child. raw, e := os.ReadFile("/proc/self/cgroup") rel, ok := ParseSelfCgroup(string(raw)) if e != nil || !ok { t.Skip("no cgroup v2 placement for this test process") } own := &Manager{root: filepath.Join("/sys/fs/cgroup", rel)} for _, line := range []string{"0::/" + rel, "0::/" + rel + "/supervise"} { fakeSelfCgroup(t, line) if e := own.StrictPreflight(); e != nil { t.Fatalf("agent at %q: preflight %v, want nil", line, e) } } fakeSelfCgroup(t, "0::/"+rel) if e := (&Manager{root: t.TempDir()}).StrictPreflight(); !errors.Is(e, ErrStrictRefused) { t.Fatalf("root outside the agent's placement: %v", e) } }