{
  "finding_id": "DF-3082",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "On the stock INVARIANTS kernel, one unprivileged file create in an htree-indexed directory of a valid e2fsck-clean ext2 image (RW-mounted by root) made ext2_direnter truncate the directory to the middle of the index tree: 65 of 100 pre-existing entries were silently destroyed on the first touch, and the next lookup of a vanished name panicked the kernel (ext2_dirbad via ext2_htree_lookup on a freed, zero-filled leaf at offset 3072). Root cause: the htree ENOENT path leaves ss/enduseful leaf-local while ext2_direnter compact-truncates to dp->i_endoff. A single-fix kernel (!ext2_htree_has_idx guard on the truncate) built in-guest eliminates both the data loss (100->140 monotonic over 40 creates, zero vanished entries) and the panic.",
  "exploit_chain": "unpriv user touch() in RW-mounted indexed dir -> lookup(CREATE) via ext2_htree_lookup ENOENT with leaf-local slot/enduseful -> notfound builds dp->i_endoff = end of searched leaf -> ext2_direnter: add_entry + ext2_truncate(i_endoff) frees all later dir blocks -> 65 entries vanish silently; dx root still indexes freed blocks -> any later lookup hashing into a freed leaf breads a zero-filled hole -> ext2_check_direntry rejects rec_len==0 -> ext2_dirbad panic (writable mount)",
  "evidence": [
    "run.log: 'after touch zzq001: entries=36' + 'lost entries: 65' (baseline data loss)",
    "panic.txt: panic ext2_dirbad ino 12 offset 3072 via ext2_htree_lookup<-ext2_search_dirblock<-ext2_lookup",
    "fix_run.log: 40 touches 100->140, vanished=0, stat sweep ok=40, FIX-OK",
    "build.log.gz: full nativekernel log, done-rc=0, 4299 cc steps"
  ],
  "kernel_refs": [
    "sys/vfs/ext2fs/ext2_lookup.c:377",
    "sys/vfs/ext2fs/ext2_lookup.c:387",
    "sys/vfs/ext2fs/ext2_lookup.c:495",
    "sys/vfs/ext2fs/ext2_lookup.c:505",
    "sys/vfs/ext2fs/ext2_lookup.c:948",
    "sys/vfs/ext2fs/ext2_lookup.c:951",
    "sys/vfs/ext2fs/ext2_lookup.c:952",
    "sys/vfs/ext2fs/ext2_htree.c:389",
    "sys/vfs/ext2fs/ext2_htree.c:394",
    "sys/vfs/ext2fs/ext2_inode_cnv.c:170",
    "sys/vfs/ext2fs/ext2_vnops.c:1229",
    "sys/vfs/ext2fs/ext2_vnops.c:1784"
  ],
  "poc_changes": "Written fresh for this run (no seed pack existed): host-side craft.sh builds the indexed image with mke2fs+debugfs+e2fsck -fD; the rm-slack step turned out to be unnecessary (e2fsck packing slack alone triggers the truncate on the first create); trigger detection compares ls counts before/after each touch.",
  "attempts": 2,
  "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 (baseline); fix kernel #1: Sun Sep  6 03:12:08 UTC 2026",
  "runtime_sec": 45,
  "guest_dirty": 0,
  "build_cmd": "host: ./craft.sh; guest root: cc -O2 -o /root/vntool /root/vntool.c; fix kernel: cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "guest root: sh mount.sh; guest maxx: sh trigger.sh",
  "code_hash": "3ad83176fe049b22e69a48b677dbb2840e5ce1732abbc443e7d13dfe4c38c175",
  "notes": "Side observation: rm of ~50 existing entries silently no-oped (lookup ENOENT for present names; consistent with dx binary-search resolving hashes below the first dx entry to the count/limit overlay when e2fsprogs writes no leading sentinel) - related to the upstream 'lost dirents' XXX at ext2_lookup.c:908-919; left for the ext2_htree.c pass. Guest reset to with-src snapshot after the runs.",
  "recommended_fix": "guard the compact-truncate in ext2_direnter with !ext2_htree_has_idx(dp) (fix.diff, validated in-guest)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep  6 03:12:08 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Single-fix kernel (only fix.diff applied to /usr/src): same image, same unprivileged user - no collapse (100->140 over 40 creates), zero vanished pre-existing entries, stat sweep of 40 surviving indexed entries ok, no panic, guest stayed up (fix_run.log).",
  "fix_evidence": [
    "fix_run.log",
    "fix_mount.log",
    "build.log.gz",
    "fix.diff"
  ]
}
