⬢ DragonFlyBSD Kernel Audit
DF-3068 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3068",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "REPRODUCED twice on the stock INVARIANTS kernel (second run after a fresh vm.sh reset with-src, clean sequence): mounting a crafted version-3 HAMMER image whose vol0_blockmap[3] names an 8-byte nominal undo range (first=UNDO|0x1000, next=UNDO|0x1008) containing a fake tail {0xC74F,0x0041,24} at 0x1000 that anchors a CRC-valid 24-byte DUMMY head at 0xff0 makes hammer_recover_stage1s undo loop consume 24 bytes where 8 remain (bytes -= hdr_size, hammer_recover.c:431) and KKASSERT(error || bytes == 0) at :460 panics: panic: assertion \"error || bytes == 0\" failed in hammer_recover_stage1 at hammer_recover.c:460, trace hammer_recover_stage1 <- hammer_vfs_mount <- sys_mount, guest wedged at db>. Root cause: hammer_check_tail_signature positions the head via the TAILs size (:1023-1026) while hammer_recover_scan_rev steps via the HEADs hdr_size (:815), and _hammer_check_signature never requires tail/head size agreement for PAD/DUMMY-style short records (:901-924 skip, :948-961 re-derive) - version<4 mounts take next_offset verbatim (:225-226). On production (non-INVARIANTS) kernels the assert is absent: the mount silently succeeds, executes a record from outside the nominal undo range, and rewrites+flushes the volume-header blockmap (:481-488) - silent mis-recovery, no memory unsafety (undo copies clamped at :1053-1076), hence Low severity. FIXED and validated: fix.diff adds hdr_size>bytes -> EIO guards in stage1s undo loop and stage2s fwd loop; on the rebuilt kernel #1 the identical image yields HAMMER(uabase) Corrupt UNDO record size 0018 at 3000000000000ff0 + mount: Input/output error, no panic, guest up; stock regressions (mount/RW cycle, hard-crash dirty recovery) all pass.",
 "exploit_chain": "",
 "evidence": [
  "panic.txt (clean run): HAMMER(uabase) recovery undo 3000000000001000-3000000000001008 (8 bytes)(RW) then panic: assertion \"error || bytes == 0\" failed in hammer_recover_stage1 at /usr/src/sys/vfs/hammer/hammer_recover.c:460 with trace hammer_recover_stage1 <- hammer_vfs_mount <- sys_mount <- syscall2, guest at db>",
  "panic_console_full.txt: first (accidental) run - identical panic, proving determinism",
  "run.log: padflood desync craft output - DUMMY+fake-tail written at phys 0000000001088000 + ff0, undo AFTER first=3000000000001000 next=3000000000001008, vol_version=3 mode=desync crafted OK",
  "fix_run.log (kernel #1 + fix.diff): Corrupt UNDO record size 0018 at 3000000000000ff0 + mount: Input/output error MOUNT_RC=1, no panic, guest up; plus stock regressions",
  "fix_build.log: make nativekernel KERNCONF=X86_64_GENERIC completed (kernel #1)"
 ],
 "kernel_refs": [
  "sys/vfs/hammer/hammer_recover.c:225-226",
  "sys/vfs/hammer/hammer_recover.c:393-431",
  "sys/vfs/hammer/hammer_recover.c:460",
  "sys/vfs/hammer/hammer_recover.c:814-815",
  "sys/vfs/hammer/hammer_recover.c:901-924",
  "sys/vfs/hammer/hammer_recover.c:948-961",
  "sys/vfs/hammer/hammer_recover.c:1023-1026"
 ],
 "poc_changes": "Pass-2 author wrote the PoC from scratch (padflood desync mode). The DUMMYs tail bytes double as the next fake tail so one 24-byte record satisfies both the real-tail and fake-tail decodes; CRC computed as crc32(head[0..12)) ^ crc32(bytes[16..24)) per hammer_crc_get_fifo_head. Version patched 7->3 so the nominal range comes straight from the crafted blockmap; no vol_crc fixup needed (DF-3042).",
 "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",
 "runtime_sec": 1500,
 "guest_dirty": 0,
 "build_cmd": "sh build.sh (cc -O -o padflood padflood.c -I/usr/src/sys/vfs/hammer; truncate -s 12G + newfs_hammer base image)",
 "run_cmd": "sh run3068.sh",
 "code_hash": "9dee8b15160f8fa2963263bf24c926c06bbf765cd9c89cc33cda337119e6f35f",
 "notes": "Deterministic INVARIANTS-only panic (dos bucket); production kernels silently mis-recover instead - no memory corruption, all undo copies bounds-checked. Sibling asserts reachable via the same desync: :394, stage2 :698/:716, and the rterm KKASSERT at :1286. Same trust boundary as the rest of the hammer crafted-image family (root mount or vfs.usermount=1). Guest reset (with-src) after validation.",
 "recommended_fix": "Reject records whose hdr_size exceeds the remaining recovery bytes (EIO) in stage1s undo loop and stage2s forward loop before executing them; see fix.diff",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 5 22:10:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Identical PoC on the patched kernel: the new guard fires at exactly the crafted record (Corrupt UNDO record size 0018 at 3000000000000ff0), mount fails with I/O error, no panic, guest stays up (vm.sh status: up). Stock regressions pass incl. hard-crash dirty recovery.",
 "fix_evidence": "fix_run.log, fix_build.log (kernel #1 build), fix.diff"
}