# DF-2775 VERDICT — reproduced (leak primitive + panic; fix validated)

## Bottom line

The unsynchronized `sysctl_intrnames` walk is a real use-after-free read
reachable by an **unprivileged** reader of `hw.intrnames` whenever any
kernel-side register/unregister activity runs concurrently on another CPU.
On the stock INVARIANTS guest this reproduced two ways:

1. **Content-controlled arbitrary-address string read (deterministic).**
   A harness copy of the exact stock walk (kern_intr.c:1106-1110), stalled
   between loading a rec pointer and dereferencing `rec->name` (a stand-in
   for any preemption/interrupt the real reader suffers there), resumed
   after the rec was unlinked and freed and immediately reclaimed by a
   same-size `M_DEVBUF` kmalloc filled with a chosen `rec->name`:
   - pointing `name` at the harness token printed
     `dfh0/.../dfh7/DF2775-SECRET-LEAK-TOKE` into the reader's entry;
   - pointing `name` at the kernel `version` string
     (0xffffffff811473a0, no KASLR on this guest) printed
     `dfh0/.../dfh7/DragonFly 6.5-DEVELOPME`.
   Reclaim rate: 100% (kfree -> kmalloc LIFO on the same cpu,
   3/3 rig runs, reclaimed=1/2/3, miss=0).

2. **Organic kernel panics from the unprivileged reader (4/4 runs).**
   With the real `sysctl hw.intrnames` hammered by an unprivileged reader
   (uid 1001, pinned cpu0 via a root launcher because USCHED_SET_CPU needs
   privilege) and the churn thread on cpu1 performing the interposed
   unregister (~200k unlinks/s), the kernel panicked every time within
   seconds with the identical signature
   `Fatal trap 9 ... Stopped at strlen: cmpb $0,(%rdi)` on cpuid 0 (the
   reader's cpu) — strlen is called from `ksnprintf("%s", rec->name)`
   (kern_intr.c:1107-1108) on a rec freed by the concurrent unregister.
   See panic.txt, logs/boot.after4thpanic.log, logs/hits.log.

## Honest quantification

- Deterministic rig: 3/3 successful token/version captures.
- Organic panic: 4/4 runs (each within ~1-60 s at ~200k deeds/s and
  ~1000-1900 reads/s).
- Organic *token-in-reader-output*: **0** observations. Mechanism: in the
  stock loop the rec-pointer load (`rec = rec->next`) and the
  `rec->name` dereference (argument evaluation of ksnprintf) are a few
  instructions apart (~5 ns). A content-controlled read needs the reader
  to be preempted exactly inside that window; without preemption the
  organic outcomes are benign (rec still valid) or crash (rec freed and
  its memory already sprayed/slab-flushed), which is exactly what the
  four panics show. The rig widens that window to prove the primitive.
- Baseline with no churn: 0 anomalies across all reader runs
  (logs/hits.log `t=1s reads=1927 hits=0` line, pre-churn).

## Privilege assessment (team directive)

- Reader side: genuinely unprivileged (hw.intrnames is 0444; systat/vmstat
  use it). 
- Churn side: register_int/unregister_int are kernel-internal APIs; an
  unprivileged user cannot drive them directly. Real-world churn comes
  from root-driven driver activity (interface create/destroy, USB
  hotplug, kldload/kldunload, VPN/pps teardown).
- Ceiling: kernel heap disclosure (bounded by NUL termination of
  whatever the reclaimed rec->name points at) plus kernel panic (local
  DoS). **No write primitive, no unpriv-alone trigger -> not a privesc.**
  Severity Medium as filed is right. On this KASLR-less guest any
  disclosed kernel pointer would also defeat symbol randomization, but
  that does not change the privilege boundary.

## Fix validation (mandatory: memory-safety finding reproduced)

`fix.diff` (pack root; identical copy in DF-2776/DF-2777 packs) rewrites
`sysctl_intrnames` to snapshot the rendered names per-CPU under the same
migrate+crit protocol `unregister_int` uses for the unlink, and performs
`SYSCTL_OUT` only after the snapshot. Applied to a pristine tree, kernel
rebuilt and booted (build.fixed.log.gz in DF-2777 pack):

- identical organic PoC (reader 90 s cpu0-pinned uid 1001; churn 85 s;
  17,408,000 deeds at the same ~205k/s rate, 100% reclaim):
  `SUMMARY reads=113091 hits=0`, **no panic** (stock: panic in seconds).
- fully-real mode-0 churn (real register_int/unregister_int storm,
  9.2M iterations) + reader 45 s: `reads=5829 hits=0`, no panic.
- output correctness preserved (src/chk.c):
  `rc=0 bytes=10218 entries=1536 e508=[dfp0/dfp1/dfp2/dfp3]`.
- the fixed protocol also defeats the deterministic stall interposition
  (`DF2775-RIG-FIXEDWALK ... token-in-output=0`).

## Kernel references

- sys/kern/kern_intr.c:1100-1116 (unsynchronized walk)
- sys/kern/kern_intr.c:1106-1110 (rec load -> rec->name use)
- sys/kern/kern_intr.c:438-448 (unlink under crit on owning cpu only)
- sys/kern/kern_intr.c:485-487 (kfree of rec->name/rec after migrate-back)

## Rig honesty notes

- The harness kernel adds `kern/df_harness.c` plus (for DF-2777) two
  dormant hook calls in kern_intr.c; the DF-2775/DF-2776 rigs on kernel
  K1 used a 100% stock kern_intr.c (verified by diff before build).
- The interposed unregister performs only unlink+kfree and writes the
  groom exclusively into memory it owns (the successful LIFO reclaim);
  no wild post-free stores.
- The organic panics on stock occurred with the fully-real
  register_int/unregister_int pair (first panic run) and with the
  interposed single-target deed (persist mode); all four signatures
  identical.

## 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.
