# DF-5824 — inferior() strands a PHOLD on proc0 per failed setpgid (unbounded p_lock growth)

## What

`inferior()` (sys/kern/kern_proc.c:470-493) walks the target's ancestry
holding each node; when the walk reaches the pid==0 node it returns 0
**without PRELE** on that node:

```c
475:  PHOLD(p);
476:  lwkt_gettoken_shared(&p->p_token);
477:  while (p != curproc) {
478:      if (p->p_pid == 0) {
479:          lwkt_reltoken(&p->p_token);
480:          return (0);           /* <-- PHOLD(proc0) stranded */
481:      }
482:      p2 = p->p_pptr;
483:      PHOLD(p2);                /* hold transfers each iteration */
...
```

Every process descends from init (pid 1) whose parent is proc0, so **every
`inferior()` call that returns 0 leaks exactly one hold on proc0**.  The
unprivileged entry is `sys_setpgid` (sys/kern/kern_prot.c:372):
`setpgid(<any pid that is not our descendant>, 0)` → `pfind` ok →
`inferior()` walks to proc0 → returns 0 → ESRCH.

`p_lock` holds `PLOCK_ZOMB|PLOCK_WAITRES|PLOCK_WAITING` in bits 28-30 with
`PLOCK_MASK == 0x0FFFFFFF` (kern_proc.c:263-266); `phold()` is a plain
`atomic_add_int(&p->p_lock, 1)` (kern_proc.c:339-342).  After 2^28 leaked
holds the count overflows into the flag bits of proc0's lock word.

## Result on the guest (run.log)

* 10,000,000/10,000,000 `setpgid(1,0)` calls returned ESRCH (the leak
  path) in 6.5 s → **~1.54M stranded holds/sec, unprivileged**.
* 300,000,000 more in 194.7 s → **proc0 p_lock grew by 310,000,000
  (0x1274F0C0) — past PLOCK_MASK (0x0FFFFFFF): the PLOCK_WAITRES flag bit
  is now literally set in proc0's p_lock.**
* Guest remained healthy throughout (proc0 is immortal and nothing waits
  on its p_lock today), i.e. the corruption is real but currently dormant.

## Why it still matters

The flag word of the kernel's primary synchronization primitive for
process reaping is corrupted by an unprivileged syscall at ~1.5M/s; any
future code that PSTALLs/pholdzombs proc0 (or a refactor that makes the
pid==0 walk reach a reapable proc) turns this into a wedge/panic.  The
counter also never wraps back cleanly (it cycles through all flag
combinations as it passes 2^29, 2^30, 2^31...).

## Fix

One line: `PRELE(p)` before the `return (0)`:

```diff
 	if (p->p_pid == 0) {
 		lwkt_reltoken(&p->p_token);
+		PRELE(p);
 		return (0);
 	}
```

## Files

`df5824_leak.c`, `build.sh`, `run.sh`, `run.log`, `VERDICT.md`,
`manifest.json`, `verdict.json`, `fix.diff`
