DF-3047 / verdict.json
{ "finding_id": "DF-3047", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "ext2_reload()/ext2_compute_sb_data() adopt a new group count and re-allocate e2fs_gd/e2fs_contigdirs (leaking the old ones) but never resize e2fs_maxcluster/e2fs_clustersum; reload step 3 then writes past both arrays and bzero()s cs_sum pointers read OOB, and ext2_unmount() later free()s them. Reproduced: mount(2) MNT_UPDATE|MNT_RELOAD after swapping the backing device content to a larger-geometry image -> fatal supervisor write in memset at heap-junk address 0x28bf.", "exploit_chain": "RO-mount crafted 8-group ext2 on user-owned vn device -> rewrite backing file with valid 16-group ext2 -> mount(2) MNT_UPDATE|MNT_RELOAD (allowed for the mount owner, incl. unprivileged with vfs.usermount=1) -> compute_sb_data sets gcount=16, arrays still sized 8 -> step3: int32 writes past e2fs_maxcluster + bzero(garbage cs_sum,33) -> panic (demonstrated); heap-groomed variant yields controlled-address zeroing + arbitrary free at unmount (characterized, not pursued to uid0)", "evidence": "run.log (full session incl. df/sb before-after), panic.txt (fault virtual address 0x28bf, supervisor write, memset+0x2b), fix_run.txt (fixed: EINVAL, clean unmount), craft.py, reloadtrigger.c", "kernel_refs": ["sys/vfs/ext2fs/ext2_vfsops.c:834", "sys/vfs/ext2fs/ext2_vfsops.c:620", "sys/vfs/ext2fs/ext2_vfsops.c:647", "sys/vfs/ext2fs/ext2_vfsops.c:947", "sys/vfs/ext2fs/ext2_vfsops.c:1057", "sys/kern/vfs_syscalls.c:259"], "poc_changes": "wrote reloadtrigger.c because DFly mount(8) has no -o reload (MNT_RELOAD is syscall-only); guest lacks vnconfig so vntool.c performs VNIOCATTACH; two-run instrumentation (df + raw sb peek) proved the device serves the swapped content before triggering.", "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": 420, "guest_dirty": 0, "build_cmd": "python3 craft.py <dir> ; cc -O2 -o /root/vntool /root/vntool.c ; cc -O2 -o /root/reloadtrigger /root/reloadtrigger.c", "run_cmd": "/root/vntool attach 0 /root/dev128.img; mount -t ext2fs -o ro /dev/vn0 /mnt/e2; cat /root/imgB128.img > /root/dev128.img; /root/reloadtrigger /mnt/e2", "code_hash": "be15d93ca1e09d01734254e3cbd495041b2215b455a88fee8ab4d1066b3febc4", "notes": "failed-reload variant (invalid larger sb) also poisons the mount: gcount committed at ext2_vfsops.c:620 before checks at :622/:681/:686, making unmount free OOB pointers. Every reload leaks the old gd+contigdirs arrays (fixed by hunk 3).", "recommended_fix": "gate ext2_reload() on identical superblock geometry and free the prior e2fs_gd/e2fs_contigdirs in ext2_compute_sb_data() before re-allocating", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 (same kernel; ext2fs.ko rebuilt with fix.diff hunks 3+4, 2026-09-05)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed module: reload with changed geometry returns EINVAL (mount: Invalid argument), guest healthy, unmount clean, no panic; reload with unchanged geometry still succeeds rc=0 with data intact; pristine RW mount regression-checked. Module build completed after adding sys/disk.h include (first build attempt failed on incomplete struct disk).", "fix_evidence": "findings/poc/DF-3047/fix_run.txt; findings/poc/DF-3047/fix.diff" } |