DF-0793 / run.log
DF-0793 reproduction + fix validation runs
==========================================
Guest: DragonFly 6.5-DEVELOPMENT x86_64, vtnet0 10.0.2.15, 6 vCPU/4GB.
Test device: asyncd0 (root-loaded KLD providing a deliberately-slow async TRIM
disk; READ/WRITE synchronous, BUF_CMD_FREEBLKS deferred 2s via callout) โ
supplies the async-device precondition that QEMU's vn/vbd devices lack.
================================================================================
RUN 1 โ UNPATCHED BASELINE (kernel #0 06:02:54, the audit-source kernel)
================================================================================
Setup: asyncd0 loaded, newfs /dev/asyncd0s0, mount("ufs",MP,MNT_TRIM) via
direct syscall (bypasses mount_ufs userland da-only gate).
Action: write 2MB blob, unlink, umount x5 loops, sleep 4s.
[0] umount done
[1] umount done
[2] umount done
[3] umount done
[4] umount done
DONE 5
RACE_EXIT=0
FREEBLKS count on asyncd0: 0 <-- TRIM PATH NEVER TAKEN
guest: up (no panic)
Timed single-shot: mount=4ms unlink=5ms unmount=251ms
=> ffs_blkfree takes the SYNCHRONOUS branch; 0 FREEBLKS bios ever sent.
DF-0793 UAF does NOT reproduce (the vulnerable code path never executes).
================================================================================
KERNEL-INSTRUMENTATION PROOF (why the TRIM path is dead)
================================================================================
A diagnostic kernel (ffs_blkfree kprintf) on a ufs+trim mount showed, for 48
ffs_blkfree calls:
DF0793 ffs_blkfree: devvp=0xfffff801170a2100
devvp->v_mount=0xfffff8008db3c800 <- device vnode's v_mount (devfs)
ip->i_vnode->v_mount=0xfffff8008eddb800 <- the ACTUAL ufs mount (DIFFERENT)
devvp_vmtrim=0 ipvmount_trim=1
=> ffs_blkfree (ffs_alloc.c:1670) reads mp = ip->i_devvp->v_mount, which is the
devfs mount of the device vnode and NEVER carries MNT_TRIM. The real ufs
mount (ip->i_vnode->v_mount) HAS MNT_TRIM, but it is never consulted.
So the `if (!(mp->mnt_flag & MNT_TRIM))` gate at ffs_alloc.c:1673 is always
TRUE -> synchronous ffs_blkfree_cg -> return. The whole store-pointers ->
async bio -> swi-task mechanism (ffs_alloc.c:1616-1697) is DEAD CODE.
Root cause of the false positive == root cause of "FFS async TRIM is
non-functional": wrong mount pointer in the MNT_TRIM gate.
================================================================================
RUN 2 โ "EXPOSE" KERNEL (mount-pointer fix ONLY: mp = ip->i_vnode->v_mount)
================================================================================
Proves the finding's mechanism is REAL-but-LATENT: fixing only the masking
mount-pointer bug makes the TRIM path reachable.
Action: write 2MB blob, unlink, umount x3 loops.
[0] umount done
[1] umount done
[2] umount done
DONE 3
RACE_EXIT=0
FREEBLKS count on asyncd0: 405 <-- TRIM PATH NOW REACHABLE
deferred completions: 405
guest: up (no panic)
Timed single-shot (2s async delay): mount=1ms write=35ms unlink=459ms
unmount(rc=0)=2129ms
Timed single-shot (4s async delay): unmount(rc=0)=3931ms
=> umount latency tracks the async bio delay EXACTLY (2s->2.1s, 4s->3.9s):
an IMPLICIT wait in the umount path (device buf drain) holds umount until
in-flight FREEBLKS bios biodone(), which lets the deferred swi tasks
(ffs_blkfree_trim_task) run while fs is still valid -> no crash observed.
The latent UAF window (biodone -> swi-task-run vs kfree(fs)) is real but
tight; it did not fire in these runs.
================================================================================
RUN 3 โ FULL-FIX KERNEL (mount-pointer fix + explicit um_trim_pending drain)
================================================================================
fix.diff applied. Action: write 2MB blob, unlink, umount x5 loops.
[0] umount rc=0
[1] umount rc=0
[2] umount rc=0
[3] umount rc=0
[4] umount rc=0
DONE 5
EXIT=0
FREEBLKS count on asyncd0: 675 <-- TRIM now FUNCTIONAL (mount-pointer fix)
guest: up (no panic)
Timed single-shot: mount=33ms write=172ms unlink+unmount(rc=0)=2932ms
Total FREEBLKS (incl timed run): 810 ; deferred completions: 810
=> The fix (a) makes async TRIM operational (FREEBLKS flows) and (b) adds an
EXPLICIT per-mount drain (tsleep on um_trim_pending) so umount provably
waits for every deferred ffs_blkfree_trim_task before kfree(fs/ump). No
panic, no warning, clean umount, TRIM working.
================================================================================
CONCLUSION
================================================================================
* DF-0793 as filed (reachable UAF on current master): NOT REPRODUCED. The
async-TRIM store-then-defer code is unreachable because ffs_blkfree checks
ip->i_devvp->v_mount (devfs, no MNT_TRIM) instead of ip->i_vnode->v_mount.
* Root cause = a real bug: the wrong mount pointer makes the ENTIRE FFS
async-TRIM feature dead/non-functional.
* Latent UAF: confirmed real (TRIM path executes once the mount pointer is
fixed); an implicit umount buf-wait masks it in practice, but the missing
taskqueue_drain is a genuine defect.
* fix.diff fixes BOTH: corrects the mount pointer (TRIM correctness) AND adds
an explicit pending-TRIM drain in ffs_unmount (closes the latent UAF).
Validated on a built+booted kernel.