⬢ DragonFlyBSD Kernel Audit
DF-4806 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4806",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "ppiopen() registers ppiintr with the cdev_t as the handler argument while ppiintr treats it as a device_t (ppi.c:268 vs 187); the first dereference chain (device_get_parent at struct-device offset 0x28 lands on the cdev TAILQ linkage, then ppb_rctr/PPBUS_IO derefs) faults at interrupt level - Fatal trap 12 at ppiintr+0x41, fault VA 0x0, all CPUs stopped. As shipped this is defused by a second bug (ppi.c:160-161 allocates the legacy IRQ non-shareably, which always fails against ppcs shareable holding), so the 1284 peripheral interrupt path is dead; fixing that allocation alone (the lpt-identical RF_SHAREABLE one-liner, arming.diff) arms the panic, which reproduced live on the QEMU guest ppi0/ppc0 at 0x378 irq7. fix.diff (RF_SHAREABLE + ppidev argument) validated: baseline armed module panics on the first strobe, patched module survives the identical trigger with the handler registered and servicing interrupts.",
  "exploit_chain": "",
  "evidence": "panic.txt (clean armed run: Stopped at ppiintr+0x41: movq (%rax),%rsi, Fatal trap 12, interrupt context); panic-diag.txt (instrumented run: ppiintr entered, arg=0xfffff80116a12e00 immediately before the fault); stock_run.log (stock RF_ACTIVE: no handler registered, 8 strobes survived); fixrun.log (fix.diff: 8 strobes survived, handler registered interrupt uses mplock: ppi0, vmstat -i ppc0=8); arming.diff/fix.diff; modbuild.sh + ppi_intr_panic.c",
  "kernel_refs": [
    "sys/dev/misc/ppi/ppi.c:160",
    "sys/dev/misc/ppi/ppi.c:245",
    "sys/dev/misc/ppi/ppi.c:266",
    "sys/dev/misc/ppi/ppi.c:187",
    "sys/dev/misc/ppc/ppc.c:1882",
    "sys/dev/misc/lpt/lpt.c:385",
    "sys/dev/misc/lpt/lpt.c:755",
    "sys/kern/subr_rman.c:341",
    "sys/bus/ppbus/ppbio.h:74",
    "sys/sys/bus_private.h:109",
    "sys/sys/conf.h:67"
  ],
  "poc_changes": "PoC authored fresh (no seed). Fixed module-build issues found on the way: kmod CFLAGS is clobbered by sys/conf/kmod.mk:98 (must use COPTS for -DPERIPH_1284), ppc needs isa_if.h/opt_ppc.h, ppi must kldload BEFORE ppc so bus_generic_identify runs pre-attach, and the arming must be plain RF_SHAREABLE (RF_ACTIVE|RF_SHAREABLE still fails - verified).",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 1500,
  "guest_dirty": 0,
  "build_cmd": "sh modbuild.sh {stock|armed|fixed}; cc -O -Wall -o ppi_intr_panic ppi_intr_panic.c",
  "run_cmd": "kldload ppbus/ppbus.ko && kldload ppi/ppi.ko && kldload ppc/ppc.ko && ./ppi_intr_panic",
  "code_hash": "d5fb59068e4911f88174b7e354cfe171392653d87674da7b04c9a669fa347c3d",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline armed module (stock ppi.c + RF_SHAREABLE only): Fatal trap 12 in ppiintr+0x41 on the first parallel-port strobe with /dev/ppi0 open. Patched module (fix.diff: RF_SHAREABLE + ppiintr arg = ppidev): identical trigger runs to completion (8 strobes), handler registered (interrupt uses mplock: ppi0) and servicing irq7 (vmstat -i ppc0=8), guest healthy.",
  "fix_evidence": "fixrun.log (patched run output + vmstat), panic.txt (baseline panic), VERDICT.md section Fix (validated)",
  "notes": "Guest /usr/src md5-identical to audit tree (e23bc3ab027648e56e1521126aee572f for ppi.c). /dev/ppi0 perms verified crw------- root:wheel. PERIPH_1284 is LINT64-only (not in X86_64_GENERIC/GENERIC), so the panic is gated on that kernel option plus a functional IRQ allocation; the allocation bug alone (dead 1284 interrupt path) affects every PERIPH_1284 kernel as shipped.",
  "recommended_fix": "Allocate the legacy IRQ with RF_SHAREABLE (mirroring lpt.c:385) AND pass ppidev (device_t), not dev (cdev_t), as the ppiintr handler argument; also guard irq>0 and check BUS_SETUP_INTRs return."
}