DF-5799 / verdict.json
{ "finding_id": "DF-5799", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "hammer2_cleanup_devvp() unconditionally clears v_rdev->si_mountpoint on every device list it processes. On the routine secondary-PFS mount path (hammer2_vfsops.c:1360, runs even when the secondary mount subsequently FAILS) it strips the marker from devices backing live hammer2 mounts; on the vfs_mountedon() EBUSY path (hammer2_vfsops.c:1096) it strips the marker from devices owned by other filesystems (ufs/msdos/ext2/cd9660/ntfs/hpfs/udf). si_mountpoint is the only state behind vfs_mountedon() (double-mount guard) and the devfs raw-write gate (devfs_vnops.c:1007-1022): after the trigger, open(O_WRONLY) of the mounted raw device succeeds for any credential with node write access \u2014 proven on the guest by dd returning Device busy before and succeeding after both trigger variants (hammer2-live and UFS-live devices), including a 200MB full-device zeroing under two live RW mounts. The marker lifecycle also parks the first mounts struct mount *, leaving a stale-pointer dereference in the devfs gate after that mount unmounts. HAMMER1 performs the correct ownership check (hammer_ondisk.c:362-366); hammer2 does not. Not a direct uid=0 chain: a protection removal that makes mounted-filesystem media attacker-writable (crafted-metadata memory-corruption surface, softdep corruption, fs destruction).", "exploit_chain": "1) any hammer2 mount attempt touching the victim device (root, or usermount-privileged user; routine multi-PFS mounting triggers it silently, and failures trigger it too) -> 2) cleanup_devvp clears the devices si_mountpoint -> 3) open(/dev/<dev>, O_WRONLY) now passes the devfs mounted-device gate (uid-independent) -> 4) arbitrary raw writes under live RW mounts (hammer2 or foreign fs): crafted on-disk metadata into kernel parsers, softdep state corruption, device/fs destruction; double-mount un-blocked for filesystems without ffss vcount guard.", "evidence": "run.log steps [3]/[5]/[6]/[9]: dd Device busy -> rc=0 after secondary mount / failed secondary mount / failed h2-mount over UFS device; run.fixed.log: all three return Device busy on the patched kernel with mounts still working; build.log: clean kernel build with fix.diff; VERDICT.md table.", "kernel_refs": [ "sys/vfs/hammer2/hammer2_ondisk.c:216", "sys/vfs/hammer2/hammer2_ondisk.c:225", "sys/vfs/hammer2/hammer2_ondisk.c:691", "sys/vfs/hammer2/hammer2_vfsops.c:1092", "sys/vfs/hammer2/hammer2_vfsops.c:1096", "sys/vfs/hammer2/hammer2_vfsops.c:1358", "sys/kern/vfs_subr.c:1887", "sys/vfs/devfs/devfs_vnops.c:1007", "sys/vfs/devfs/devfs_vnops.c:1018", "sys/vfs/hammer/hammer_ondisk.c:362", "sys/vfs/ufs/ffs_softdep.c:4266" ], "poc_changes": "poc.sh is a self-contained csh script (vnconfig + newfs_hammer2 + mount + dd gate probes); no seed code was provided for this pass-3 finding.", "attempts": 3, "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": 2400, "guest_dirty": 0, "build_cmd": "make -j6 nativekernel KERNCONF=X86_64_GENERIC (fix validation)", "run_cmd": "csh /root/poc.sh (see run.sh: scp + vm.sh run_root)", "code_hash": "3bf70c8a96e1527d442a3564af773d224a910505a56ef91b53f74a7b9cf10bb2", "notes": "Fixed kernel validated end-to-end (build rc=0, install, reboot into #1, all three gate bypasses gone, no mount regression). vnconfig -u Device busy after unmount is a pre-existing vn disk opencount quirk observed identically on the unpatched kernel (fstat clean) \u2014 not a fix regression. Guest reset to clean stock kernel after validation.", "recommended_fix": "Remove the unconditional si_mountpoint clear from hammer2_cleanup_devvp(); manage the marker with ownership-checked helpers (hammer2_devvp_mount/_unmount/_unmount_all) called from mount/unmount mount_count transitions, mirroring hammer1s si_mountpoint == hmp->mp check; also fixes the stale struct mount * in the devfs gate.", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff applied in guest /usr/src, kernel rebuilt (nativekernel rc=0), installed, rebooted; identical poc.sh re-run: dd returns Device busy at all previously-bypassed steps ([5],[6],[9]) while primary+secondary PFS mounts and UFS mount all still succeed \u2014 bypass eliminated, no functional regression.", "fix_evidence": "findings/poc/DF-5799/fix.diff (git-applyable); build.log (35772 lines, clean); run.fixed.log vs run.log comparison table in VERDICT.md" } |