DF-3078 / verdict.json
{ "finding_id": "DF-3078", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on stock #0: MNT_RELOAD (mount(2) MNT_UPDATE|MNT_RELOAD|MNT_RDONLY on a RO-mounted UFS) re-reads the superblock and adopts its geometry (ffs_vfsops.c:500) while fs_csp/fs_maxcluster/um_fs keep their mount-time sizes (allocated at :671/:685). Crafted re-read sb gives: heap OOB WRITE with fully attacker-controlled length AND content (cssize variant: 524288 pattern bytes bcopy'd into the 2053-byte fs_csp chunk, reload returned 0, ~522KB heap silently smashed); unbounded OOB write of controlled value (ncg variant: vm_fault stack-guard panic in the *lp++ refill); divide-by-zero (fsize=0: Fatal trap 18 idivl at ffs_mount+0x627); heap OOB READ past um_fs via adopted fs_sbsize at RW-upgrade ff_sbupdate bcopy (sbsize=65536: Fatal trap 12 supervisor-read in memmove; sbsize=8192: the 6144 bytes beyond um_fs written to the raw media). Adoption in returning reloads proven via crafted fs_dsize showing in df. NOT an unpriv->root LPE: trigger is root-gated (get_fscap maps ufs to SYSCAP_RESTRICTEDROOT; vfs.usermount=1 does not help โ same blocker empirically proven in the DF-0820 pack). Impact ceiling: root-context kernel heap corruption + kernel-heap disclosure to media; panic DoS demonstrated.", "exploit_chain": "", "evidence": [ "findings/poc/DF-3078/run.log โ full baseline transcript (all variants)", "findings/poc/DF-3078/panic_idivl_fsize0.txt โ Fatal trap 18 idivl (fsize=0 adopted)", "findings/poc/DF-3078/panic_maxcluster_ncg.txt โ vm_fault stack-guard panic (ncg=0x20000000 refill store)", "findings/poc/DF-3078/panic_sbupdate_oob_read.txt โ trap 12 supervisor READ in memmove (sbsize=65536 sbupdate OOB read)", "findings/poc/DF-3078/run.patched.log โ fix validation: all crafted variants EINVAL on #1, regressions pass", "findings/poc/DF-3078/build.log, env.txt, fix.diff" ], "kernel_refs": [ "sys/vfs/ufs/ffs_vfsops.c:479", "sys/vfs/ufs/ffs_vfsops.c:484", "sys/vfs/ufs/ffs_vfsops.c:493", "sys/vfs/ufs/ffs_vfsops.c:495", "sys/vfs/ufs/ffs_vfsops.c:500", "sys/vfs/ufs/ffs_vfsops.c:515", "sys/vfs/ufs/ffs_vfsops.c:526", "sys/vfs/ufs/ffs_vfsops.c:536", "sys/vfs/ufs/ffs_vfsops.c:671", "sys/vfs/ufs/ffs_vfsops.c:685", "sys/vfs/ufs/ffs_vfsops.c:1292", "sys/vfs/ufs/ffs_vfsops.c:1295" ], "poc_changes": "Trigger written from scratch: DF's mount_ufs has no '-o reload' option, so the PoC calls mount(2) directly with MNT_UPDATE|MNT_RELOAD|MNT_RDONLY and a zeroed >=sizeof(struct ufs_args) data buffer (a first draft passed a too-small struct and would have made ffs_mount's copyin over-read user memory). craft.c/dumpsb.c built on-guest with -I/usr/src; sb field offsets verified in-guest (fs_cssize=156, fs_fsize=52, fs_ncg=44, fs_sbsize=104, fs_dsize=40, fs_fmod=208).", "attempts": 9, "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": 3300, "guest_dirty": 0, "build_cmd": "cd /root/rt && cc -o craft craft.c && cc -I/usr/src -o dumpsb dumpsb.c && cc -o reload reload.c", "run_cmd": "sh run.sh <unchanged|cssize|fsize0|ncg|sbsize_leak|sbsize_fault> (guest root; see README)", "code_hash": "8c248f01eaf8720192f80f1e6c4f974de69a358aac79d064cbcbe6a193c73854", "notes": "Related: DF-0820 (initial-mount geometry census โ NOT re-filed; different function/root-cause), DF-3047 (ext2 MNT_RELOAD analog), DF-3015 (maxsymlinklen trust boundary gains a second import path at :504). fs_fmod adoption alone is NOT a reachable rofs-mod panic: sys_sync's callback (vfs_syscalls.c:1199) and the syncer (vfs_sync.c:731) both skip RDONLY mounts โ killed empirically (95+ s with fs_fmod=1 adopted, 60 sync()s, no panic). Guest reset clean (vm.sh reset with-src) after validation.", "recommended_fix": "ffs_reload: reject any re-read superblock whose sizing geometry differs from the mounted one (fs_sbsize/bsize/fsize/frag/ncg/ipg/cssize/csaddr/contigsumsize/bshift/fshift) with EINVAL, and force fs_fmod=0 โ see fix.diff; validated on a single-fix kernel.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Sun Sep 6 02:02:00 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to in-guest /usr/src, make -j6 nativekernel KERNCONF=X86_64_GENERIC + installkernel (BUILD_OK), rebooted into #1: all five crafted variants now return 'mount: Invalid argument' (EINVAL) with the guest staying up and the filesystem still accessible; unchanged-sb reload returns 0; fresh RW mount + file create works. Before/after matrix in VERDICT.md and run.patched.log.", "fix_evidence": [ "findings/poc/DF-3078/run.patched.log", "findings/poc/DF-3078/build.log", "findings/poc/DF-3078/fix.diff" ] } |