โฌข DragonFlyBSD Kernel Audit
DF-0793 / run.log
โ† back to finding โ†“ download raw
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.