⬢ DragonFlyBSD Kernel Audit
DF-5787 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5787",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Reproduced: a buffer header released with a stale b_bio1.bio_flags BIO_DONE carries that flag across bqrelse -> bufqueue -> getblk(found-path) reacquisition, and the next bread() on the buffer panics at the unconditional KKASSERT((bio->bio_flags & BIO_DONE) == 0) in vn_strategy (sys/kern/vfs_bio.c:3385); on a kernel without that guard, _biowait (vfs_bio.c:3261) instead returns before the device I/O completes, so the caller consumes b_data during DMA and brelse()s a buffer whose biodone/bpdone then runs on a requeued or reused buffer (queue corruption / double accounting). Root cause: getnewbuf() resets only b_flags (vfs_bio.c:2095), reinitbufbio() clears bio_done/bio_offset only (vfs_bio.c:747-750), getblk()s found path never touches bio_flags, and breadnx()/bwrite() arm completion flags with |= (vfs_bio.c:919-920, 989-990). Reachability requires a kernel-side completion path that releases a bio with BIO_DONE set (forgetful breadcb/cluster_readcb callback - the contract hammer_indirect_callback honors by hand - or biowait_timeout abandonment, e.g. sys/dev/disk/fd/fd.c:2279); no in-tree trigger found, so this is a Low-severity kernel-robustness/defense-in-depth defect, verified with a root-loaded KLD rig; fix.diff (clear bio_flags in reinitbufbio + assignment arming in breadnx/bwrite) was built into the guest kernel and the identical rig then ran both cases with no panic.",
  "exploit_chain": "No unprivileged chain: the trigger is a kernel-internal contract violation (buggy/abandoning bio completion path), not user input. Chain that was demonstrated: KLD emulates breadcb()s cached-path leftover state (BIO_DONE set at release, vfs_bio.c:883) -> buffer recycled through bqrelse/getblk with flag intact -> bread() -> vn_strategy KKASSERT panic at vfs_bio.c:3385 (baseline) -> with fix.diff, bread() completes normally and _biowait clears the flags.",
  "evidence": "panic.txt (panic text + backtrace vn_strategy<-breadnx<-bpio_case), run.log (full serial: control case passes, poison case panics, same bp address 0xfffff8004f7442d0 in both), fix_run.log (patched kernel: biopoison[1]: bread returned 0 (no panic), guest alive), build.log (full module build), fix.diff (3 hunks), VERDICT.md (narrative + killed-candidates list)",
  "kernel_refs": [
    "sys/kern/vfs_bio.c:2095",
    "sys/kern/vfs_bio.c:747",
    "sys/kern/vfs_bio.c:919",
    "sys/kern/vfs_bio.c:989",
    "sys/kern/vfs_bio.c:883",
    "sys/kern/vfs_bio.c:3261",
    "sys/kern/vfs_bio.c:3385",
    "sys/vfs/hammer/hammer_io.c:1640",
    "sys/kern/kern_physio.c:68"
  ],
  "poc_changes": "Authored from scratch (no seed): KLD module biopoison.c resolves a hammer2 file vp via nlookup/cache_vref, then runs a control case (getblk, clear B_CACHE, bqrelse, bread) and a poison case (same + stale b_bio1.bio_flags |= BIO_DONE before bqrelse) on load. Makefile needs SYSDIR=/usr/src/sys for out-of-tree kmod.mk builds on the guest.",
  "attempts": 2,
  "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": 900,
  "guest_dirty": 0,
  "build_cmd": "cd /root/biopoison && make   # Makefile: KMOD=biopoison SRCS=biopoison.c SYSDIR=/usr/src/sys .include \"${SYSDIR}/conf/kmod.mk\"",
  "run_cmd": "kldload /root/biopoison/biopoison.ko   # observe serial console / vm.sh log",
  "code_hash": "522d8a1b82ca7f52da3afc8aed6625f4de6a1fc68ecdabd35f2ccafb3a2dc2d2",
  "notes": "Low severity: no in-tree trigger (all current completion paths clear BIO_DONE by hand); demonstrated consequence of the missing kernel-side defense is a deterministic panic on an always-KKASSERT kernel, and a premature-biowait/DMA race on one without. Same |= arming pattern exists in vfs_cluster.c cluster_read sync setup and kern_physio.c:71; the reinitbufbio half of fix.diff covers the physio pbuf case, the vfs_cluster.c sites would deserve the same assignment treatment upstream.",
  "recommended_fix": "Clear embedded bio flags at buffer-cache recycling and arm sync completion with assignment instead of OR: reinitbufbio() sets bio->bio_flags = 0; breadnx()/bwrite() set bp->b_bio1.bio_flags = BIO_SYNC (see fix.diff).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied inside guest /usr/src, quickkernel rebuilt (vfs_bio.o relinked, kernel #1 Mon Oct 12 16:00:55), installkernel + reboot, identical module re-run: control case passed and poison case returned bread=0 with NO panic and the guest alive - the baseline panic at vfs_bio.c:3385 is gone.",
  "fix_evidence": "fix_run.log (biopoison[1]: bread returned 0 (no panic) + bread SURVIVED, guest uptime continues); fix_build.log (quickkernel/installkernel output, vfs_bio.c recompiled); fix.diff",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Oct 12 16:00:55 UTC 2026  root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64"
}