# DF-5823 VERDICT

**status: not_reproduced** (runtime) / race statically **certain** /
impact: none observed / confidence: likely

## Mechanism (path:line)

* Filters run under only `prg->proc_token` shared:
  sys/kern/kern_proc.c:1710-1711 (`lwkt_gettoken_shared`, `LIST_FOREACH`).
* KERN_PROC_PGRP re-reads `p->p_pgrp` twice with no lock:
  kern_proc.c:1736-1738.
* KERN_PROC_TTY walks `p_pgrp->pg_session->s_ttyp->t_dev`:
  kern_proc.c:1741-1747.  `p_session` is `#define p_session
  p_pgrp->pg_session` (sys/sys/proc.h:344) so the `p->p_session == NULL`
  check at 1743 is dead code — the NULL-ness it pretends to guard is one
  hop deeper.
* The pgrp pointer is mutated by `enterpgrp()` (kern_proc.c:814-826) and
  `leavepgrp()` (kern_proc.c:859-872) under `p->p_token`+`pg_token`, and
  the last `pgrel()` frees the pgrp (kern_proc.c:656-690) while the member
  proc remains scannable on allproc.  The mover's manual `pgref()`
  (kern_proc.c:815, 860) protects only its own window, not a scanner that
  already loaded the pointer.
* Zombies keep `p_pgrp` until reap (`leavepgrp` at kern_exit.c:1271 runs
  after `proc_remove_zombie`), and list removal is serialized against the
  scan by the bucket token — so the *freed-pgrp* variant (member moves
  pgrps while on-list) is the reachable race; a NULL `p_pgrp` is not
  reachable for on-list procs.

## What was run

`./df5823_churn 180` as unprivileged `maxx` on the audit guest: 24 churn
workers, each forking grandchildren that `setpgid(0,0)` (fresh pgrp, sole
member, on-list) then `setsid()` (leaves that pgrp as last member → pgrel
frees it mid-scan); half the workers additionally acquire a controlling
tty (posix_openpt + auto-TIOCSCTTY) so the `s_ttyp` hop of KERN_PROC_TTY
is raced.  Scanner polled `kern.proc.pgrp.1` and `kern.proc.tty.0`
continuously: 17,090,388 iterations (~94,942/s), load average 22.

## Why no panic (honest impact ceiling)

Every object in the raced chain (`struct pgrp`, `struct session`, `struct
tty`) lives in a dedicated kmalloc zone whose backing memory is never
unmapped; freed chunks retain (or get same-type) contents, so the stale
reads degrade to wrong `pg_id`/`t_dev` comparisons instead of faults.  A
page-faulting wild hop requires a freed chunk field to alias a
non-resident pointer — possible only through same-zone reuse patterns
that keep producing valid objects.  Consequence class: incorrect filter
results (cosmetic), theoretical panic (not demonstrated in 17M polls).

## Reproduce

```
cc -O2 -o df5823_churn df5823_churn.c
./df5823_churn 180
# guest stays up, rig prints "scanner iterations: NNNN (~95k/s)"
# a kernel panic during the run would upgrade this finding
```

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
