{
  "finding_id": "DF-3172",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Reproduced on the stock INVARIANTS guest: ffs_indirtruncs tail-zering bzero (ffs_inode.c:535-536) takes its start index from the truncate length (lastbn/factor, ffs_inode.c:311-314,487) and its length from NINDIR(fs) = fs->fs_nindir \u2014 raw superblock data that ffs_mountfs never validates (ffs_vfsops.c:642-646 checks only fs_magic and bsize bounds; fs_nindir is adopted verbatim into ump->um_nindir at :729). Patching ONE int32 in the superblock (fs_nindir 1024->512 on a newfs -b 4096 image) makes the mount-time fs_maxfilesize clamp (ffs_vfsops.c:753-758, lbn cap 2^30) exceed the crafted tree capacity 8x, so an unprivileged ftruncate(2) by the file owner to Sigma*4096+4096 = 550,831,706,112 drives lastiblock[TRIPLE] to exactly NINDIR^3 and the bzero computes (uint)(-1)*sizeof(ufs_daddr_t) = ~16GiB: a wild kernel zero-march off the 4096-byte indirect-block buffer -> Fatal trap 12 (supervisor write, page not present), Stopped at memset+0xd5 repe stosq, guest down (run.log, boot_after_trigger.log). An aimed variant (fs_nindir=2^28 + on-disk-crafted di_size) demonstrates attacker-chosen placement: a 4100-byte zero at b_data + 1.000 GiB -> panic with rip = memset+0xf0 (panic_aimed.txt). The same unvalidated fs_nindir also OOB-reads in ffs_balloc.c:297 via ufs_bmap.c:315 in_off=(lbn/..)%M (reproduced: ffs_balloc+0x5e7 panic) and in the ffs_inode.c:550-563 free loop. Fixed by fix.diff (mount validation + representable maxfilesize clamp + in-file last clamp): validated in-guest with make nativekernel \u2014 crafted mounts rejected EINVAL, legit 560GB sparse truncate works, no panic.",
  "exploit_chain": "root mounts crafted media (fs_nindir patched) -> unprivileged file owner ftruncates a file whose di_size exceeds the crafted geometrys representable range -> ffs_indirtrunc bzero with (start=b_data+(lastbn+1)*4, len=(uint)(M-(lastbn+1))*4) \u2014 start attacker-chosen within a ~4GiB window above a buffer-cache KVA address, length arbitrary (mod 2^32) -> kernel zero-write primitive. uid0 NOT completed: zero-only primitive anchored in the buffer KVA submap (machdep.c:499-505, vfs_bio.c:638 \u2014 64K windows, only resident pages mapped) marches at most to the first non-resident page; proc/ucred slabs are not contiguous with it; the aimed variant could in principle target a sprayed ucred on this no-KASLR guest but needs a KVA disclosure companion (only candidate: the ffs_inode.c:550-563 OOB-read -> ffs_blkfree -> cg-bitmap oracle, aborted by INVARIANTS before usable samples) and zeroes intervening live objects before a root shell can exec. Documented as a rigorous hard blocker in VERDICT.md; impact recorded as deterministic panic + controlled corruption window.",
  "evidence": [
    "findings/poc/DF-3172/run.log \u2014 full baseline run: setup, unpriv extend to 560GB, single-int32 craft, shrink -> Fatal trap 12 supervisor write, Stopped at memset+0xd5 repe stosq, fault VA 0xfffff80066de7000",
    "findings/poc/DF-3172/boot_after_trigger.log \u2014 serial console capture of the wild-march panic",
    "findings/poc/DF-3172/panic_aimed.txt \u2014 aimed variant: panic assertion obj!=NULL with rip=memset+0xf0 (the bzero itself faulted at the aimed +1GiB target)",
    "findings/poc/DF-3172/run.aimed.log / run.patched.log \u2014 aimed run + sibling ffs_balloc OOB-read panic + full patched-kernel validation matrix (T1 regression OK, T2/T3 crafted mounts EINVAL)",
    "findings/poc/DF-3172/trigger3172.c + setup1.sh + setup2.sh + craft64.c \u2014 minimal unprivileged trigger + image builder + single-int32 superblock patcher",
    "findings/poc/DF-3172/aim3172.c + inopatch.c + mkaim.c + setupB.sh \u2014 aimed (+1GiB) variant harness",
    "findings/poc/DF-3172/fix.diff + fix_build.log \u2014 mount validation + maxfilesize representable clamp + in-file clamp; 35848-line in-guest kernel build, 0 warnings"
  ],
  "kernel_refs": [
    "sys/vfs/ufs/ffs_inode.c:535",
    "sys/vfs/ufs/ffs_inode.c:311",
    "sys/vfs/ufs/ffs_inode.c:157",
    "sys/vfs/ufs/ffs_inode.c:550",
    "sys/vfs/ufs/ffs_vfsops.c:642",
    "sys/vfs/ufs/ffs_vfsops.c:729",
    "sys/vfs/ufs/ffs_vfsops.c:753",
    "sys/vfs/ufs/ufs_bmap.c:315",
    "sys/vfs/ufs/ffs_balloc.c:297",
    "sys/vfs/ufs/ufs_types.h:41",
    "sys/platform/pc64/x86_64/machdep.c:499"
  ],
  "poc_changes": "Seed authored fresh in this run. Two false premises were corrected by experiment: (1) NINDIR is fs_bsize/sizeof(ufs_daddr_t) with ufs_daddr_t=int32 (bsize/4 = 1024 for 4K blocks, not 512) \u2014 the first variant (crafted fs_maxfilesize alone, honest nindir) did NOT fire because the mount clamp 2^30*bsize-1 exactly equals the honest tree capacity in lbns; (2) with crafted nindir the kernel cannot grow the file past the crafted geometry (ufs_bmap.c:283 EFBIG + ffs_balloc.c:297 OOB panic), so the oversized di_size is placed on disk (inopatch) under the legit geometry and the superblock is patched afterwards. Aimed variant derived after observing that start=(lastbn+1)*4 with factor=1 at SINGLE level gives a ~4GiB aiming window.",
  "attempts": 7,
  "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": 9600,
  "guest_dirty": 0,
  "build_cmd": "cc -o trigger3172 trigger3172.c   (in-guest; plus craft64/inopatch/mkaim/aim3172 built the same way)",
  "run_cmd": "vm.sh run_root sh setup1.sh; vm.sh run_user /tmp/trigger3172 /mnt/poc/d/f extend; vm.sh run_root sh setup2.sh; vm.sh run_user /tmp/trigger3172 /mnt/poc/d/f shrink  # -> panic",
  "code_hash": "72c8d2fbae57c1d4f1fc1ced2860e2cf4e3b35f8270cb19845fd53364e8e1597",
  "notes": "fix_status=fixed: fix.diff applied to guest /usr/src, make nativekernel (0 warnings), kernel #1 booted; T1 regression (legit 4K fs, unpriv 560GB->550831706112 truncate) returns 0 with exact final size and guest up; T2 (fs_nindir 1024->512) and T3 (fs_nindir=2^28) crafted superblocks rejected incorrect super block (EINVAL) at mount, guest up, no panic. fix_kernel_uname=DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep 7 17:51:23 UTC 2026; fix_baseline_reproduced=1; fix_patched_reproduced=0. Guest left clean via vm.sh reset with-src. Known-unfixed sibling in this file: DF-0887 symlink fast-path bzero (ffs_inode.c:165) still present in-tree \u2014 not re-reported. Sibling sinks ffs_balloc.c:297 and ufs_bmap.c:315 share the root cause and are covered by the same mount validation; filed here as cross-references for their own pass-2 audits.",
  "recommended_fix": "Validate fs_nindir == fs_bsize/sizeof(ufs_daddr_t) at mount (ffs_mountfs), cap fs_maxfilesize at the representable Sigma*bsize-1 (mkfs.c formula), and clamp ffs_indirtruncs `last to NINDIR(fs)-1 (defense in depth, also covers MNT_RELOAD geometry re-adoption)."
}