โฌข DragonFlyBSD Kernel Audit
DF-2626 / verdict.json
โ† back to finding โ†“ download raw
{
  "finding_id": "DF-2626",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "CONFIRMED on the stock guest (root fs hammer2): hammer2_read_file() (hammer2_vnops.c:926-985) discards uiomovebp()'s return at :983; on a user-page fault at byte 0 uiomove() (kern_subr.c:148-153) returns EFAULT without advancing uio_resid/uio_offset, so the loop predicate at :926 can never change and the thread spins in kernel mode forever holding the vnode lock SH (vfs_vnops.c:751) and ip->truncate_lock SH (:922). Empirically: an unprivileged read() of any readable hammer2 file into a PROT_NONE page pins one CPU at 100% system (STAT R, wchan '-') and the process SURVIVES two kill -9 attempts โ€” unkillable. readlink() is an equal vector (:790). Six readers on six distinct hammer2 files starved all 6 CPUs: ssh dead, serial console silent, no panic โ€” full system DoS requiring power cycle. tmpfs control returns EFAULT immediately (console: 'tmpfs_read uiomove error 14'), isolating the defect to hammer2's read loop. fix.diff (propagate the error, bqrelse preserved, break) was rebuilt in-guest (nativekernel) and validated: the identical PoC binary returns -1/EFAULT in 0.00 s on the patched kernel, with 50/50 good/bad read regression and the 6-file wedge pattern leaving 0 spinners and 98.9% idle. fix_status=fixed.",
  "exploit_chain": "",
  "evidence": [
    "run.log: pid 988 uid 1001 STAT R2 100.0% CPU wchan '-' โ€” '>>> STILL ALIVE after kill -9 <<<' twice; top 'CPU2 99.85% df2626poc'",
    "serial_wedge2.log: serial console ends at '=== DF-2626 WEDGE2 ... ncpu=6' then total silence; wedge2.log + vm.sh status => down (all-CPU starvation, no panic)",
    "readlink_stock.log + serial_readlink_stock.log: single unprivileged readlink() wedged the stock box within ~60 s (ssh banner timeout, console silent)",
    "wedge1.log: tmpfs control โ€” same PoC on tmpfs returns -1/EFAULT instantly, kernel console prints 'tmpfs_read uiomove error 14'",
    "fix_run.log: patched kernel #1 (20:29:47), identical binary md5 โ€” 'read returned -1 errno=14' in 0.00 real, POC_EXIT=0",
    "fix_regress.log: good_reads=50 efault_reads=50; 'spinners left: 0'; CPU 98.9% idle after the 6-distinct-file pattern"
  ],
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_vnops.c:983",
    "sys/vfs/hammer2/hammer2_vnops.c:926",
    "sys/vfs/hammer2/hammer2_vnops.c:922",
    "sys/vfs/hammer2/hammer2_vnops.c:986",
    "sys/vfs/hammer2/hammer2_vnops.c:790",
    "sys/kern/kern_subr.c:148",
    "sys/kern/vfs_vnops.c:751"
  ],
  "poc_changes": "Replaced seed trigger with deterministic fault-at-byte-0: mmap(PROT_NONE, MAP_FIXED) at 0x10000000 instead of a raw bad pointer ((void*)0x10000 can be mapped). Added readlink variant (same loop via hammer2_vop_readlink). Fixed the guest harness for DragonFly: no setsid(1)/procstat(1) on DF; su -m must not carry csh redirect syntax; backgrounded jobs need </dev/null to release the ssh pipe; all evidence teed to /dev/console to survive ssh death.",
  "attempts": 6,
  "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": 2580,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o poc poc.c && cc -O2 -Wall -o readlink_poc readlink_poc.c",
  "run_cmd": "./poc /etc/rc   (as unprivileged uid 1001, cwd on hammer2 root fs; ./readlink_poc likewise)",
  "code_hash": "0eaf49ab2c29b7c5cbbc95158b7f72cf641896565e07303c0d8bc5bf850e0472",
  "notes": "DoS only (no memory corruption โ€” no escalation chain applicable; primary objective (1) N/A by class, objective (2) met with full characterization). Per-CPU unkillable spin per read()/readlink() call; distinct files => one CPU each; all CPUs => silent starvation (no panic; needs hard reset; even root cannot kill offenders). Same-file readers interleave in getblk (100% sys total, box limping) โ€” distinct files remove the last sleep point. Write path at hammer2_vnops.c:1144 already checked the same error โ€” the read loop was the miss. In-guest fix_build.log was lost to the final snapshot revert; fix_build.log preserves verbatim poll excerpts (flagged). Guest left clean: final vm.sh reset with-src, stock kernel #0, no spinners.",
  "recommended_fix": "In hammer2_read_file(): capture the uiomovebp() result and break on error โ€” 'error = uiomovebp(bp, bp->b_data + loff, n, uio); bqrelse(bp); if (error) break;' (see fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Aug 28 20:29:47 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to guest /usr/src (patch -p1, hunk at :980), kernel rebuilt with make -j6 nativekernel KERNCONF=X86_64_GENERIC + make installkernel (hammer2_vnops.c compiled clean under -Werror), rebooted into kernel #1 (Fri Aug 28 20:29:47 UTC 2026). Baseline on stock #0: livelock reproduced (unkillable 100%-CPU spin; 6-file pattern wedges the box). Patched #1: the IDENTICAL PoC binary (md5 ed5f88f7...) returns -1/EFAULT in 0.00 s with exit 0 (fix_run.log), readlink variant likewise (fix_readlink.log), and regressions pass โ€” 50/50 good reads of /etc/rc unchanged, 50/50 faulting reads return EFAULT, the 6-distinct-file wedge pattern leaves 0 spinning processes and the box 98.9% idle (fix_regress.log). The previously-observed bad behavior is GONE with no read-path regressions: fix_status=fixed.",
  "fix_evidence": "fix_build.log (patch applied at :980; hammer2_vnops.o -Werror clean; BUILD_INSTALL_OK; kernel #1 20:29:47); fix_run.log (-1/EFAULT, 0.00 real, POC_EXIT=0); fix_readlink.log; fix_regress.log (good_reads=50 efault_reads=50, spinners left: 0, 98.9% idle)"
}