# DF-5624 VERDICT — REPRODUCED (panic; fixed by fix.diff)

## Bottom line

`hard_sniff()` (the Xsniff flame-graph sampler) bounds-checks the frame-pointer
chain with `rbp > top - 8` but then dereferences `rbp + 8`, so a frame pointer
exactly at `top - 8` — admitted by the guard — makes the kernel load
`[top, top+8)`, 8 bytes past the end of the interrupted thread's kernel stack
(`sys/platform/pc64/x86_64/mp_flame.c:136-138`). On a guest DragonFly
6.5-DEVELOPMENT kernel this was reproduced twice as a **fatal trap 12 page
fault inside `hard_sniff` (`hard_sniff+0x9e: movq 0x8(%rdx),%rax`) panicking
the machine**, with an instrumented rig proving causality end to end, and the
one-line guard fix (`top - 8` → `top - 16`) was built, installed, and shown to
eliminate both the panic and the silent out-of-bounds capture.

## Reproduction narrative (all on the audit QEMU/KVM guest)

1. **Direct proof the guard admits the boundary (mapped neighbour):** KLD
   `flameoob.ko` calls the *production* `hard_sniff()` with a crafted
   trapframe `tf_rbp = top - 8` of the current thread's kstack. Guest output
   (`run.sh`): guard inputs printed (`tf_rbp=0xfffff801166dbff8 == top-8`,
   8-aligned, >= bot), entry recorded (`windex` 0→1, `rips[0]` = magic), the
   OOB qword captured in `rips[1]`. Across a 24-load sampling loop one
   neighbour contained live kernel heap: the module's reference read printed
   `qword at [top, top+8) = 203a343236354644` = ASCII `"DF5624: "` — kprintf
   scratch — i.e. adjacent-heap disclosure is real, not theoretical.
2. **Panic proof #1 (v1 rig, incidental):** during the nonzero-neighbour hunt
   the loop hit a kstack followed by an unmapped page:
   `kstack=[0xfffff80118810000,0xfffff80118814000)`, fatal fault at
   `0xfffff80118814000` == top, `Stopped at hard_sniff+0x9e: movq
   0x8(%rdx),%rax` (`panic-v1-module.txt`). 43 loads this boot, 1 unmapped
   neighbour → ~1/43 occurrence rate under ordinary process churn.
3. **Panic proof #2 (v2 rig, instrumented, matched to the fix run):** v2 adds
   a safe read-only 4-level page-table walk (through the DMAP, present-bit
   checked at every level — cannot itself fault) so the console records
   whether the neighbour page is mapped *before* `hard_sniff` is called.
   Baseline loop: `NEIGHBOR UNMAPPED` printed for
   `top=0xfffff801185da000`, then `Fatal trap 12 ... fault virtual address =
   0xfffff801185da000 ... supervisor read data, page not present ... Stopped
   at hard_sniff+0x9e` (`panic.txt`). Causality is now airtight: the faulting
   load is the return-address read at mp_flame.c:138 with rbp == top-8, the
   exact case the guard fails to exclude.
4. **Fix validation:** `fix.diff` (guard `rbp > top - 16`) applied to the
   guest's `/usr/src`, `make -j8 nativekernel KERNCONF=X86_64_GENERIC`
   (RC=0), `make installkernel` (RC=0), reboot into `#1: Thu Oct 8 22:24:57
   UTC 2026`. Identical v2 loop, 150 iterations: **no panic, guest stayed
   up**, ≥111 unmapped-neighbour loads survived ("survived UNMAPPED-neighbour
   sniff"), and the flame dump showed **0 entries with rips[1] ≠ 0** — the
   fixed guard refuses rbp==top-8 before the load, so no OOB read occurs at
   all (`run_fixed.log`). Baseline vs patched behaviour differs exactly as
   the fix predicts.

## Honest impact classification

- Observation = guest dead in DDB after fatal kernel-mode page fault, ssh
  gone, `vm.sh status` ⇒ down → **reproduced / impact=panic (local DoS)**.
- The read is 8 bytes, value lands in a buffer readable only with
  `SYSCAP_RESTRICTEDROOT` — no privilege boundary is crossed by the leak.

## Exploitability (why no uid=0 chain)

The primitive is a **read-only** 8-byte overshoot whose sink (the flame entry)
is root-only. There is no write primitive, no register control, no
use-after-free; the faulting address is chosen by stack layout, not the
attacker. An unprivileged user cannot arm the sampler (sysctl writes require
`SYSCAP_NOSYSCTL_WR`, `sys/kern/kern_sysctl.c:1446-1448`); once an
administrator arms `debug.flame_graph_enable`, unprivileged activity
(including the automatic ipiq-stall sniff at `sys/kern/lwkt_ipiq.c:264,272`
and any user polling `kern.cputime` with `kern.sniff_enable=1`,
`sys/kern/kern_clock.c:161-174`) supplies the interrupts, and any sampled
thread whose kstack abuts an unmapped page crashes the machine. Hard blocker
for escalation: read-only sink ⇒ ceiling is DoS + root-only heap-qword
disclosure. Severity Medium ("local DoS requiring unusual config/privilege"
per the rubric), confidence certain.

## Kernel references

- sys/platform/pc64/x86_64/mp_flame.c:124-129 (bot/top, windex entry select)
- sys/platform/pc64/x86_64/mp_flame.c:135-143 (buggy guard :136, OOB load :138)
- sys/platform/pc64/x86_64/mp_flame.c:119-120 (enable gate), :148-176 (data
  sysctl, RESTRICTEDROOT), :182-195 (sniff sysctl, RESTRICTEDROOT)
- sys/kern/kern_sysctl.c:1439-1448 (root-only sysctl writes)
- sys/kern/kern_clock.c:161-174, sys/kern/lwkt_ipiq.c:264,272 (sniff triggers)
- sys/platform/pc64/x86_64/machdep.c:3651-3660 (twin guard bug, other file)
- sys/platform/pc64/apic/lapic.c:255 (Xsniff IDT wiring)

## Notes / discrepancies

- The task brief described this file as "tsc Frequency/PIPT frame buffer
  scrolling / mp relay-barrier helpers" — the file contains none of that; it
  is the flame-graph sampler. Audited as-found (see final JSON
  negative_notes for the classes checked and killed).
- `ebytes` header at mp_flame.c:161-162 understates the per-CPU record size
  (`sizeof(fge)*256` is what's emitted); harmless — the only consumer
  (usr.bin/flame_graph/collect.c:83-97) ignores it and uses `fg->nentries`.
- Guest left clean: `vm.sh reset with-src` after validation (guest_dirty=0).

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