carlos / platform Public

zdd plan 2 task 4 fix: the hog is bounded and its confinement checked first

The hog child now waits for a "go" line on stdin before it allocates. The
parent gives it only after reading back that g2's memory.max is 64M and
that the hog's /proc/<pid>/cgroup is g2, so a StartChild that fell back
to "spawning unaccounted" fails the test instead of letting the hog loose.

The hog is bounded: a 256 MiB populating mmap, then at most 768 MiB of
touch loop, then exit 3 with a message. A failed mmap exits 2 with the
error on stderr, and EOF on stdin exits 4, so none of them can read as
"not OOM-killed within 60s". Child stderr now goes to the test's stderr.

Re-run on DOCS-T1 under `systemd-run --user -p Delegate=yes`: both PASS
(OOM 14.20s, strict 0.05s). Plain go test: all three SKIP. The dev-box
run was not done (needs Paul's approval).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f5df5d67327a5b4862fa1ca8dd66e125eed33b90 parent f54813c
1 file changed, +43 −5
  • internal/cgroup/gen_kernel_linux_test.go +43 −5
diff --git a/internal/cgroup/gen_kernel_linux_test.go b/internal/cgroup/gen_kernel_linux_test.go
index 099674e19..217a07d52 100644
--- a/internal/cgroup/gen_kernel_linux_test.go
+++ b/internal/cgroup/gen_kernel_linux_test.go
@@ -56,6 +56,7 @@ func genKernelManager(t *testing.T) *Manager {
func genKernelCmd(mode string) *exec.Cmd {
cmd := exec.Command(os.Args[0], "-test.run=^TestGenKernelChild$", "-test.count=1")
cmd.Env = []string{"CARLOS_GEN_CHILD=" + mode}
+ cmd.Stderr = os.Stderr // a child that fails says why
return cmd
}
@@ -72,22 +73,35 @@ func TestGenKernelChild(t *testing.T) {
}
os.Exit(0)
case "hog":
+ // Wait for the parent's go-ahead, which it gives only after it has
+ // checked that this process is inside its capped cgroup. EOF means
+ // the parent is gone: exit rather than allocate unwatched.
+ if _, err := bufio.NewReader(os.Stdin).ReadString('\n'); err != nil {
+ fmt.Fprintln(os.Stderr, "hog: no go-ahead:", err)
+ os.Exit(4)
+ }
// One populating mmap: the kernel faults every page in without
// returning to user space, so memory.high's throttle (a sleep on
// each return) cannot slow the climb to memory.max. A hog that
// touches pages one at a time crawls at the throttle, swaps, and
// can sit under the limit for minutes instead of dying.
- if mem, err := syscall.Mmap(-1, 0, 512<<20, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE|syscall.MAP_POPULATE); err == nil {
- _ = mem
+ mem, err := syscall.Mmap(-1, 0, 256<<20, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE|syscall.MAP_POPULATE)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "hog: mmap:", err)
+ os.Exit(2)
}
- var hold [][]byte
- for {
+ _ = mem
+ // Bounded: a hog that survives 1 GiB in all was not confined.
+ hold := make([][]byte, 0, 192)
+ for len(hold) < cap(hold) {
b := make([]byte, 4<<20)
for i := 0; i < len(b); i += 4096 {
b[i] = 1
}
hold = append(hold, b)
}
+ fmt.Fprintln(os.Stderr, "hog: survived 1 GiB: not confined")
+ os.Exit(3)
default:
t.Skip("synthetic child only")
}
@@ -217,10 +231,34 @@ func TestGenOOMKernel(t *testing.T) {
}
expectFile(t, filepath.Join(parent, "memory.max"), "134217728")
- hog, err := m.StartChild(host, GenChild(2), small, small, func() *exec.Cmd { return genKernelCmd("hog") })
+ var hogIn io.WriteCloser
+ hog, err := m.StartChild(host, GenChild(2), small, small, func() *exec.Cmd {
+ cmd := genKernelCmd("hog")
+ hogIn, _ = cmd.StdinPipe()
+ return cmd
+ })
if err != nil {
t.Fatal(err)
}
+ // StartChild is best-effort: a refused limit write starts the child
+ // uncapped. The hog waits on stdin, so prove it is confined first.
+ confined := func(ok bool, format string, args ...any) {
+ t.Helper()
+ if !ok {
+ _ = hog.Process.Kill()
+ _ = hog.Wait()
+ t.Fatalf(format, args...)
+ }
+ }
+ g2 := filepath.Join(parent, GenChild(2))
+ wantG2 := filepath.Join(strings.TrimPrefix(m.Root(), "/sys/fs/cgroup/"), ExecName(host), GenChild(2))
+ got, err := os.ReadFile(filepath.Join(g2, "memory.max"))
+ confined(err == nil && strings.TrimSpace(string(got)) == "67108864", "g2 memory.max = %q, %v; want 67108864 before the hog runs", got, err)
+ gotCg := cgroupOf(t, hog.Process.Pid)
+ confined(gotCg == wantG2, "hog runs in %s, want %s", gotCg, wantG2)
+ if _, err := fmt.Fprintln(hogIn, "go"); err != nil {
+ confined(false, "hog go-ahead: %v", err)
+ }
waited := make(chan error, 1)
go func() { waited <- hog.Wait() }()
select {