⬢ DragonFlyBSD Kernel Audit
DF-3170 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3170",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Unprivileged concurrent file I/O on a mounted MFS corrupts the mfs bio queue: mfsstrategy enqueues under the BGL while mfs_start dequeues BGL-free under only a local critical section (MNTK_ST_MPSAFE is set), so producer and consumer share a lockless TAILQ with no common lock. Two independent stock-kernel panics reproduced from unprivileged writers (consumer-side Bad link elm prev->next != elm in mfs_start after ~3 min; producer-side Bad tailq NEXT(...->tqh_last) != NULL in mfsstrategy under 90 s). On non-INVARIANTS kernels the same interleavings are a dropped bio (unkillable biowait hang) and a stale-tqh_last pointer store into recycled kernel memory. Spinlock fix validated on two patched in-guest kernels: 11+ min of baseline-equivalent stress (4-6M iterations per writer, two mounts, ENOSPC churn) with zero panics and normal mount/IO/umount lifecycle intact.",
  "exploit_chain": "unpriv writers -> O_SYNC pwrite/pread on mounted MFS -> ufs_strategy -> dev_dstrategy(BGL) -> mfsstrategy TAILQ_INSERT_TAIL racing mfs_starts bioq_takefirst on another CPU -> TAILQ corruption (panic on INVARIANTS). uid0 chain rigorously blocked: the corruption primitive is an uncontrolled kernel-pointer store through a dangling tqh_last that lands inside recycled buffer-cache struct-buf memory (kernel-managed reuse, fixed offset, value = bio/ buf address, not attacker-chosen); no allocator path exposes that slot to credential-bearing objects, so no credible pivot to ucred/proc was found. Primary impact: reliable unprivileged kernel panic/DoS + latent heap corruption on production kernels.",
  "evidence": [
    "panic.txt (Bad link elm, mfs_start stack, cpuid 5)",
    "panic_run2.txt + run2_panic_serial.log (Bad tailq NEXT, mfsstrategy/ufs_strategy stack, cpuid 2)",
    "run.log (full baseline sequence)",
    "fix_run.log (patched-kernel stress, no panic)",
    "VERDICT.md"
  ],
  "kernel_refs": [
    "sys/vfs/mfs/mfs_vfsops.c:184-189",
    "sys/vfs/mfs/mfs_vfsops.c:452-463",
    "sys/vfs/mfs/mfs_vfsops.c:401",
    "sys/sys/buf2.h:141-175",
    "sys/kern/kern_device.c:117-122",
    "sys/kern/kern_device.c:354-389",
    "sys/kern/vfs_vfsops.c:101-118",
    "sys/sys/mount.h:406-425"
  ],
  "poc_changes": "PoC authored fresh for this finding (mfsrace.c: 6 processes x 2 files of 512-byte O_SYNC pwrite/pread at rotating offsets). Guest-side: launch with nohup + full redirects so writers survive ssh session close (the naive 2 > file form leaks stderr onto the session and SIGHUPs writers); keep total file set <= 12MB on a 16MB MFS so writers never ENOSPC.",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); patched #1 Mon Sep 7 16:11:15 UTC 2026 / #2 16:51:08",
  "runtime_sec": 3180,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o /tmp/mfsrace /tmp/mfsrace.c (in-guest, unprivileged)",
  "run_cmd": "root: (mount_mfs -s 32768 swap /mnt/mfs &); user: 6x nohup /tmp/mfsrace /mnt/mfs 2",
  "code_hash": "d02997210c139f518ab44d7b5b97c6fcd37823e8370458c8f0c4dfe1f1d36b7e",
  "notes": "Second stock panic was initially misattributed to the DF-3171 teardown hypothesis and has been reattributed here with a full honesty record in the DF-3171 pack. Fix validation kernel also carried DF-3171s guard (same function); both builds stress-verified.",
  "recommended_fix": "Serialize mfsp->bio_queue with a per-mfsnode spinlock in mfsstrategy/mfsclose/mfs_start (producers self-complete ENXIO when inactive; final drain in mfs_start), replacing the BGL/crit non-pairing.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep  7 16:51:08 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline stock kernel panicked twice under the writer stress (consumer- and producer-side TAILQ INVARIANTS checks). Patched kernel (build #1 and rebuilt #2) survived 11+ minutes of the identical stress at equal or higher op rates plus ENOSPC churn and a forced-devfs-unmount kill construction, with all writers progressing monotonically and all three MFS servers alive; normal mount/IO/umount lifecycle intact (server exits via EMOUNTEXIT, /dev/mfsN removed). No panic, no wedge, no lost bios.",
  "fix_evidence": "findings/poc/DF-3170/fix_build.log, findings/poc/DF-3170/fix_run.log, findings/poc/DF-3171/fix_run.log [2]"
}