DF-3188 / verdict.json
{ "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" } |