{
  "finding_id": "DF-2649",
  "status": "not_reproduced",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "Code-proven NULL dereference not materializable on the QEMU guest. hammer2_bulkfree_scan guards only HAMMER2_ERROR_CHECK (bulkfree.c:135) before unconditionally dereferencing parent->data->ipdata.filename for PFSROOT inodes in its two kprintf()s (bulkfree.c:143-147 and :393-398); hammer2_chain_load_data leaves chain->data == NULL with error = EIO when bread() fails (hammer2_chain.c:1003-1008). Reachability of the crash precondition was demonstrated: a forged PFSROOT INODE bref in the mounted PFS's root-inode blockset drives the unprivileged (ungated, DF-0815) bulkfree scan into hammer2_chain_lock -> hammer2_chain_load_data -> _hammer2_io_bread on the poison chain (panic_getvolume.txt backtrace). But an in-window failing read cannot be produced on a validly mounted volume: hammer2 enforces volu_size <= media_size at mount (hammer2_ondisk.c:283-290; the inflated-volu_size variant is rejected, mount_reject.log), and out-of-volume offsets panic earlier in hammer2_get_volume (already filed as DF-0875). The triggering condition therefore requires real unreadable media (failing disk sector under a PFS-root inode), turning a routine bulkfree into a kernel panic instead of an error report. Not reproduced per the honest-status table; the finding stands on line-proven analysis plus demonstrated reachability up to the exact load site.",
  "exploit_chain": "",
  "evidence": [
    "panic_getvolume.txt: unprivileged run on craft2649_v1.img -> panic: no volume for offset 0x100000000 with backtrace hammer2_get_volume <- _hammer2_io_getblk <- _hammer2_io_bread <- hammer2_chain_load_data <- hammer2_chain_lock (proves the scan recursed into and locked the forged PFSROOT chain; DF-0875 manifestation)",
    "mount_reject.log: volu_size-inflated variant rejected at mount ('size 0x8000000 exceeds device size 0x4000000', hammer2_ondisk.c:283-290) - closes the forged-image route to an in-volume EIO",
    "VERDICT.md: full 3-attempt narrative incl. the key-sorting discovery (hammer2_base_find requires sorted blockset slots)",
    "fix.diff: parent->data == NULL guard (covers EIO and zero-data_off chains at both kprintf sites)"
  ],
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_bulkfree.c:135",
    "sys/vfs/hammer2/hammer2_bulkfree.c:143-147",
    "sys/vfs/hammer2/hammer2_bulkfree.c:393-398",
    "sys/vfs/hammer2/hammer2_chain.c:1003-1008",
    "sys/vfs/hammer2/hammer2_chain.c:2876-2879"
  ],
  "poc_changes": "Seed concept extended: poison bref placed in the mounted PFS's own root-inode blockset (mount never walks it, bulkfree does); key must sort above the dirent key; out-of-volume data_off manifests DF-0875 before the EIO can be observed; volu_size inflation is blocked by the mount-time device-size cross-check (whose DIOCGPART-failure skip is itself an ondisk.c-scope gap, not re-filed here).",
  "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": "guest: truncate -s 64M base.img && newfs_hammer2 -L testvol base.img && (mount, f1, sync, umount); host: python3 forge_2649.py base2649.img craft2649_volusize.img eio (+ inline v1 regenerator in build.sh); guest: cc -O -o trigger trigger.c",
  "run_cmd": "vnconfig -c vn0 /tmp/craft2649_v1.img && mount -t hammer2 /dev/vn0@testvol /mnt/h2x && su -m nobody -c '/tmp/trigger /mnt/h2x/f1'",
  "code_hash": "323b5e2fdc23e610313b34d03ab4aec2e350a7cec755cba3b453db9dc12aabf9",
  "notes": "Fix compiled into the validation kernel together with DF-2650's fix (no regression; healthy mounts unaffected), but the triggering EIO cannot be produced on vn-backed media, so fix behavior is unobservable in-guest: on craft2649_v1.img the fixed kernel still panics in hammer2_get_volume (DF-0875, out of this fix's scope).",
  "recommended_fix": "In hammer2_bulkfree_scan(), skip the chain (goto done with error |= EIO) when parent->data == NULL after the CHECK guard, before the PFSROOT kprintf at :143-147; the same guard protects :393-398.",
  "fix_status": "not_testable",
  "fix_kernel_uname": "",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff compiled and booted in the validation kernel (see DF-2650 fix_build.log / fix_run.log for the shared build); the triggering condition (in-volume bread EIO) cannot be produced on the QEMU guest, so the guard's behavior change is not observable there; no regression on healthy media.",
  "fix_evidence": ["DF-2650/fix_build.log", "DF-2650/fix_run.log"]
}
