# DF-2951 VERDICT

**Status: not_reproduced (runtime). Code-level protocol violation: certain
by inspection. Severity filed: Low, confidence likely.**

## Why the finding is real (source argument)

1. Protocol: `td_ucred` is the syscall-context credential,
   synchronized from `p_ucred` at user->kernel transition
   (sys/sys/thread.h:279, :327). `cratom_proc` (kern_prot.c:1166-1188)
   documents that racing readers are exactly why the pointer store is
   `p_spin`-interlocked — raw unlocked `p->p_ucred` loads outside that
   protocol are the violation. The sysctl core itself reads
   `td->td_ucred` (kern_sysctl.c:1447); `sysctl_hostname` deviates.
2. kern_mib.c:222 caches `pr` from one `p->p_ucred` load, then :228-229
   re-load `p->p_ucred` (three more loads: the handler argument and the
   two `sizeof`-expression operands, evaluation order unspecified).
   Between any load and its deref, `cratom_proc` on another thread can
   drop the last non-cached reference and `crfree` the cred
   (crfree → refcount 0 → ucred freed; its `cr_prison` field then holds
   whatever the objcache recycler wrote).
3. Consequence scale: `pr_host` is `char[256]` (sys/jail.h:118) — the
   subsequent `SYSCTL_OUT` copies `strlen(pr_host)+1` bytes from a
   garbage-derived prison pointer to userspace. Realistic outcome:
   kernel panic (wild read). Theoretical ceiling: 256-byte disclosure
   if the stale chunk is groomed. Prison-UAF half of the window is dead
   in practice (prison0 is static; a dynamic prison is pinned by the
   process's own current cred), so the ucred-object UAF-read is the
   whole story.
4. Attacker: jailed uid-0 (setgroups allowed in jail → unconditional
   `cratom_proc`, kern_prot.c:682; kern.hostname read ungated).
   Unprivileged non-jail users cannot replace their own p_ucred
   (no-op setuid/setgid, privileged setgroups), which bounds severity
   at Low — same actor class and impact ceiling family as DF-0181's
   jailed-root model, but probabilistic instead of deterministic.

## Verification attempt (honest negative)

stress_ucred_race.c: 4 threads looping `sysctlbyname("kern.hostname")`,
4 threads looping alternating `setgroups` (forcing cratom replacement
every call), run for 3 × 120 s inside a jail as uid 0 on the stock
INVARIANTS guest:

* run.log:  reads 124,072,534 ; setgroups 153,437,435 ; 0 errors
* run.2.log: reads 119,316,721 ; setgroups 154,727,757 ; 0 errors
* run.3.log: reads 124,101,906 ; setgroups 158,916,561 ; 0 errors

No panic, no wedge, guest stayed up (checked after each run). The race
window is a handful of adjacent instructions and the freed ucred chunk
is typically immediately re-issued as the *next* cratom's cred (same
objcache, same content), so a userspace-only trigger did not manifest.
This is a hardening/robustness fix, not a demonstrable exploit.

## Recommended fix (one-liner in spirit)

Use the cached prison once, via the sanctioned thread credential:

```c
	struct prison *pr = NULL;
	if (p) {
		pr = td->td_ucred->cr_prison;
		...
	}
	if (p && pr) {
		...
		error = sysctl_handle_string(oidp, pr->pr_host,
		    sizeof(pr->pr_host), req);
	}
```

plus optionally `spin_lock(&p->p_spin)` around a single
`td->td_ucred` capture if td_ucred is ever NULL here (it is not, for
user threads: kern_caps.c:344-350 relies on it).
