{
  "finding_id": "DF-2584",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED on the guest (impact: local DoS/panic via crafted image, unprivileged trigger). hammer2_flush_core() has no depth deferral (the list the header comment flush.c:369-373 describes was never implemented; recovery has one at vfsops.c:2278-2292); a crafted image rewrites file /fs block table into a chain of N single-entry 16KB INDIRECT blocks (check methods=0x00, key=0/kb=16, no nesting validation on the read path) ending at the original DATA block. One write into the file flags the whole chain (chain_modify -> setflush walk-up, chain.c:147-167) and sync flushes it: flush_core<->flush_recurse recurses one C-frame pair per level. On this INVARIANTS build the 16KB kernel thread stack dies between depth 35 and 40, so the crafted 40/55/60-deep images all produce Fatal double fault ... panic: double fault with rip inside hammer2_chain_load_data (called by the chain_lock in flush_recurse flush.c:1197) BEFORE the depth==60 panic check at flush.c:584 can ever fire - the explicit panic() is unreachable on this build; the missing deferral manifests as uncontrolled kernel-stack exhaustion, which is worse than the designed panic. Controls: pristine image and depth-30/35 images survive identical mount+write+sync. Canonical run: root mounts the crafted TESTFS, unprivileged uid=1001 dd-overwrites the world-writable /f and runs sync -> double fault (panic.txt, run.log). Fix validated A/B: fix.diff lowers the limit to 16 and implements the deferral list (re-drive deferred chains with depth reset); patched kernel (#1 Oct 14) survives the exact same PoC with clean sync+umount while the stock kernel panics; a first fix variant that called setflush(parent) at deferral time livelocked (pagedaemon continuous paging) and was dropped. Documented caveat: on the patched kernel the write to the 60-deep crafted chain does not persist to media (depth-10 control persists), i.e. the deferrals UPDATE/data propagation needs upstream maturing; legitimate topologies (~<=10 levels, logarithmic) never engage the deferral so real filesystems are unaffected.",
  "exploit_chain": "attacker supplies crafted hammer2 image (60-deep single-child indirect nesting under a world-writable file, parent-link check codes disabled, volheader CRCs repaired) -> victim root mounts it (or vfs.usermount=1) -> any unprivileged user with write permission on the crafted file overwrites it (dd) -> chain lookup loads all 60 indirect chains, chain_modify+setflush flags the whole path ONFLUSH/MODIFIED -> sync -> vfs_sync stage-1 per-inode flush -> hammer2_flush_core/hammer2_flush_recurse recursion exhausts the kernel thread stack -> Fatal double fault -> system down (panic). No code-execution route: the overflow terminates at the stack guard and the double-fault handler panics.",
  "evidence": "panic.txt (full serial console of the unprivileged-trigger run: DOUBLE FAULT, rip=0xffffffff8096f204 symbolized via nm to hammer2_chain_load_data); run.log (canonical uid=1001 dd+sync -> hang -> panic); run.pristine.log / run.d30.log / run.d35.log (surviving controls) vs run.d40.log / run.d55.log (double faults) - depth-driven attribution; craft_deep.py (self-verifying patcher); fix.diff + fix_build.log + fix_run.log (A/B: stock panics, patched survives with clean umount); run.persist10.log vs run.persist60.log (fix caveat); VERDICT.md (full narrative with path:line)",
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_flush.c:580",
    "sys/vfs/hammer2/hammer2_flush.c:584",
    "sys/vfs/hammer2/hammer2_flush.c:56",
    "sys/vfs/hammer2/hammer2_flush.c:1254",
    "sys/vfs/hammer2/hammer2_flush.c:1197",
    "sys/vfs/hammer2/hammer2_flush.c:369",
    "sys/vfs/hammer2/hammer2_vfsops.c:2278",
    "sys/vfs/hammer2/hammer2_vfsops.c:2292",
    "sys/vfs/hammer2/hammer2_chain.c:147",
    "sys/vfs/hammer2/hammer2_strategy.c:717",
    "sys/vfs/hammer2/hammer2_vfsops.c:2717"
  ],
  "poc_changes": "PoC authored from scratch (no seed). Two patcher iterations: (1) meta.mode is a bare unix mode word (type lives in meta.type, inum at meta+0x58) - first assertion assumed S_IFMT bits; (2) added the world-writable bit (0644->0666) so the trigger runs as uid 1001 instead of root. Trigger used dd bs=32768 (the files own block size) to avoid hammer2_chain_resize on the crafted chain. Fix development required three iterations: limit-60-only deferral still double-faulted (stack dies ~35-40), limit 16 + setflush(parent) at deferral livelocked, final = limit 16 + plain deferral list.",
  "attempts": 8,
  "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": 9000,
  "guest_dirty": 0,
  "build_cmd": "guest(root): sh build_image.sh; host: python3 craft_deep.py h2.img h2c60.img 60 (also 55/40/35/30/10); fix kernel: cd /usr/src && git apply fix2584.diff && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel",
  "run_cmd": "guest(root): sh run.sh /root/h2c60.img ; unprivileged variant: root mounts /dev/vn0@TESTFS /mnt/h2, then run_user dd if=/dev/zero of=/mnt/h2/f bs=32768 count=1 conv=notrunc; sync",
  "code_hash": "48f8e3baef6e6217fdc1db535d6b2adee045711c35ccff5b99898ae588ba6cb7",
  "notes": "The findings summary said the panic occurs during mount recovery; in fact the reproducible path is mount + one write + sync (recovery is skipped on cleanly-unmounted images; nothing walks the file topology at mount). The literal panic string at flush.c:584 never executes on this INVARIMENTS build because the stack dies at depth ~35-40 < 60; both manifestations share the missing-deferral root cause. Depth sweep: <=35 survives, >=40 double-faults (4 reproductions). Incidental observation for upstream: the stock kernels HAMMER2_FLUSH_DEPTH_LIMIT=60 has always exceeded the real per-thread stack budget for this recursion. Fix caveat (crafted-media only, documented in VERDICT.md #4): deferred >16-deep chains do not persist their in-place data write on the patched kernel (depth-10 control persists); stock kernel panics on the same input so there is no working stock behavior to preserve.",
  "recommended_fix": "Lower HAMMER2_FLUSH_DEPTH_LIMIT to a value the kernel stack can actually sustain (16) and implement the documented deferral: at the limit, reference the chain, queue it on a flush-deferral TAILQ, and re-drive each queued chain from hammer2_flush() with the depth reset to 0 (see fix.diff).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline stock kernel (#0 Jul 2): crafted depth-60 image + mount + dd + sync -> Fatal double fault during sync (panic.txt). Patched kernel (#1 Oct 14, fix.diff via nativekernel): same image, same commands -> SYNC-SURVIVED, clean UMOUNT-OK, guest healthy; repeated mount/write/sync/remount cycles stable. Crash eliminated. Crafted-media caveat: the write to the deferred 60-deep chain is not persisted (file reads original bytes after remount; depth-10 no-deferral control persists correctly) - upstream should mature the deferrals UPDATE propagation; legitimate topologies (~<=10 indirect levels) never engage the deferral.",
  "fix_evidence": "fix_build.log / fix_install.log (patched kernel build), fix_run.log (A/B run), run.persist10.log vs run.persist60.log (caveat capture), panic.txt (baseline)"
}