⬢ DragonFlyBSD Kernel Audit
DF-3122 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3122",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED on the stock INVARIANTS kernel (#0): one unprivileged stat() of an absent name in an htree-indexed directory of a crafted RW-mounted ext2 image panics the kernel \u2014 panic: ext2_dirbad at offset 7168, backtrace ext2_search_dirblock <- ext2_htree_lookup <- ext2_lookup. The offset is the block pointer of entry #126 of a dx node loaded by ext2_htree_check_next (ext2_htree.c:144-151) with ZERO validation \u2014 not the count/limit cross-check find_leaf applies (:313-315), not the node-capacity check (ext2_htree_node_limit). count=0xFFFF walks h_entry through 126 crafted entries (attacker-chosen blkatoff targets) and entry #127 lives at node offset 1024 == first byte past the 1KiB buffer: ext2_htree_get_hash() (:136) reads 4 kernel-heap bytes OOB and, gate permitting, ext2_htree_get_block() (:383) reads 4 more OOB bytes as a file offset. The stage-2 OOB probe (a2.img) ran silently (3/3 fresh boots: gate rejected garbage, clean ENOENT, guest survived) \u2014 the OOB read is source-certain but invisible without heap grooming. Distinct from DF-0850: the PoCs descent node B1 is fully honest (limit==127==node_limit), so DF-0850s fix does not block this chain. Fix validated in-guest (kernel #1): both stages clean ENOENT, no panic; valid-image regression 100/100 lookups + rm works.",
  "exploit_chain": "crafted ext2 image (dir_index, RW mount by root) -> any user stats absent name -> find_leaf descends honest root+B1 -> miss in leaf -> check_next exhausts B1, advances root entry, parity gate passes, blkatoff reloads crafted B2 with no validation -> walk consumes attacker entries (count=0xFFFF unchecked) -> entry 127 read at b_data+1024 = OOB heap read -> garbage-driven blkatoff/dirbad -> kernel panic (DoS). OOB read primitive: 4-8 bytes per walk step past a 1KiB buffer-cache object.",
  "evidence": [
    "panic.txt (panic signature + backtrace, offset 7168 = crafted entry 126 blk)",
    "run.log (stock stage-1 panic)",
    "run2.log/run3.log (stock stage-2 silent OOB probes)",
    "fix_run1.log fix_run2.log (patched kernel #1: no panic)",
    "fix_valid.log (regression: ok=100, rm ok)",
    "build.log.gz (in-guest kernel build, 4299 cc, rc=0)",
    "craft.sh (deterministic image builder incl. hash port validated vs e2fsck)",
    "VERDICT.md (full trace), notes-df3085.md (DF-3085 disproof)"
  ],
  "kernel_refs": [
    "sys/vfs/ext2fs/ext2_htree.c:142-152",
    "sys/vfs/ext2fs/ext2_htree.c:127-129",
    "sys/vfs/ext2fs/ext2_htree.c:136-140",
    "sys/vfs/ext2fs/ext2_htree.c:381-415",
    "sys/vfs/ext2fs/ext2_htree.c:313-315"
  ],
  "poc_changes": "Seed sketch (from DF-3082 family) had no crafted-node machinery at all: wrote a deterministic builder (craft.sh + embedded python half_md4 port, validated 3/3 against e2fscks dumped hashes) producing honest root/B1 + unvalidated-reload node B2 (count=0xFFFF); first attempt misplaced the dx-node overlay at offset 12 \u2014 struct ext2fs_htree_nodes fake dirent is 8 bytes (no name padding), fixed to offsets 8/10/12.",
  "attempts": 8,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (stock) / #1: Sun Sep  6 21:28:18 UTC 2026 (patched)",
  "runtime_sec": 2700,
  "guest_dirty": 0,
  "build_cmd": "guest: cc -O2 -o /root/vntool /root/vntool.c ; fix build: cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel",
  "run_cmd": "guest root: /root/vntool attach 0 /root/a1.img && mount -t ext2fs /dev/vn0 /mnt/e2 && chmod 777 /mnt/e2/d ; guest maxx: stat /mnt/e2/d/zzqq01",
  "code_hash": "eb73f348525048db9da3a9ace630a489e711ca6e4419627b8f76e9cceeeb23aa",
  "notes": "Stage-2 OOB read is runtime-silent (heap-dependent) \u2014 documented honestly; demonstrated impact is the deterministic crafted-image panic (same precondition bar as DF-0850: Medium). DF-3085 lead REFUTED (notes-df3085.md): e2fsck sentinel exists on disk, 100/100 lookups + rm succeed; original symptom was a seq -w name-width artifact in DF-3082s trigger.sh.",
  "recommended_fix": "Validate the reloaded node in ext2_htree_check_next (count != 0 && count <= limit && limit == ext2_htree_node_limit(ip); brelse + return 0 on failure) \u2014 fix.diff, validated in-guest.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep  6 21:28:18 UTC 2026",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Patch applied to /usr/src in guest, kernel rebuilt (4299 cc, rc=0) and installed. Baseline panic reproduced on #0; on #1 the identical trigger returns clean ENOENT with the guest up for both a1.img and a2.img, and a valid e2fsck-indexed image still passes 100/100 lookups plus a successful rm \u2014 fix holds with no regression.",
  "fix_evidence": [
    "fix_run1.log",
    "fix_run2.log",
    "fix_valid.log",
    "build.log.gz",
    "fix.diff"
  ]
}