DF-3082 / verdict.json
{ "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" ] } |