⬢ DragonFlyBSD Kernel Audit
DF-3188 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3188",
 "status": "not_reproduced",
 "reproduced": 0,
 "impact": "none",
 "confidence": "likely",
 "verdict": "The race is structural and unprivileged-reachable but too tight to win inside the observation budget. hammer_load_pseudofs (hammer_inode.c:983-989) does an unlocked RB_LOOKUP + hammer_ref on rb_pfsm_root while hammer_rel_pseudofs (hammer_inode.c:1218-1227) does hammer_rel + a non-atomic hammer_norefs re-read + RB_REMOVE + kfree with no lock or interlock (contrast hammer_get_buffers hammer_ref_interlock protocol). The unprivileged sprayer is HAMMERIOC_GET_PSEUDOFS on a nonexistent pfs id (dispatcher overwrites the caps_priv_check gate), which runs the full kmalloc->RB_INSERT->rel->RB_REMOVE->kfree cycle per call. Two runs totaling 662,368,571 complete cycles (8 threads x 480s + 6 threads x 300s, ~840k ioctls/s, 6-vCPU guest) produced no anomaly - the transient pfsm is only findable for the few instructions between RB_INSERT and the ioctl error paths rel, and the racing side must extract-and-ref within that span. If won: UAF/double-free of a kmallocd struct hammer_pseudofs_inmem plus RB_REMOVE on an already-removed node (wild pointer writes) - memcorrupt bucket. 95% CI upper bound on per-cycle hit probability from 0/6.62e8 trials: ~4.5e-9.",
 "exploit_chain": "",
 "evidence": [
  "run.log: authentic second run (259M cycles, SURVIVED); VERDICT.md carries the first runs progress/final lines (log lost to the post-panic reset for DF-3187)",
  "VERDICT.md: interleave diagram, source cites, window analysis, and the no-serialization proof (VFS_MPLOCK is a no-op: hammer_vfsops.c:635 MNTK_ALL_MPSAFE)"
 ],
 "kernel_refs": [
  "sys/vfs/hammer/hammer_inode.c:983",
  "sys/vfs/hammer/hammer_inode.c:989",
  "sys/vfs/hammer/hammer_inode.c:1220",
  "sys/vfs/hammer/hammer_inode.c:1224",
  "sys/vfs/hammer/hammer_pfs.c:68",
  "sys/vfs/hammer/hammer_vfsops.c:635"
 ],
 "poc_changes": "none vs seed design; compiled in-guest with -pthread",
 "attempts": 2,
 "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0/#3 x86_64 (6 vCPU, 4GB)",
 "runtime_sec": 780,
 "guest_dirty": 0,
 "build_cmd": "cc -O -pthread -I/usr/src/sys/vfs/hammer -o race_getpfs race_getpfs.c",
 "run_cmd": "./race_getpfs /mnt 40000 480 8 (run 1, stock kernel); ./race_getpfs /mnt 40000 300 6 (run 2, kernel #3 - fix.diff does not touch the pfsm RB path)",
 "code_hash": "8dd834d2390117dbcc635b4be92646cbd814c02e2be058d5e0462524580f9000",
 "notes": "Race window is nanosecond-bounded (findable span of the transient pfsm x ref-gap of the racing lookup). A demonstration plausibly needs hours-to-days of dedicated spraying or a window-widening scheduling primitive; the defect itself is provable by inspection - no serialization exists on rb_pfsm_root anywhere.",
 "recommended_fix": "Serialize the pfsm RB protocol: a mount-level spinlock around RB_LOOKUP+hammer_ref (and RB_INSERT) in hammer_load_pseudofs and around the norefs check + RB_REMOVE in hammer_rel_pseudofs, or adopt the hammer_ref_interlock()/hammer_rel_interlock() pattern already used for buffers",
 "fix_status": "not_testable"
}