DF-3187 / verdict.json
{ "finding_id": "DF-3187", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Unprivileged HAMMERIOC_SCAN_PSEUDOFS (dispatcher drops the caps_priv_check gate, hammer_ioctl.c:264) on a filesystem whose PFS record leaf carries a forged data reference drives hammer_btree_extract() (hammer_btree.c:736-770, production-no validation; INVARIANTS KKASSERT compiled out even on this guests kernel) to CRC kernel memory through a wild pointer derived from the on-disk data_offset/data_len. Reproduced twice on the stock kernel (#0 Jul 2): Fatal trap 12, supervisor read fault at calculate_crc32c+0xf0, full stack hammer_ioc_scan_pseudofs -> hammer_ip_resolve_data -> hammer_btree_extract -> hammer_crc_test_leaf -> iscsi_crc32; crash dump inspected live (cursor->data=0xfffff800523f9fff unaligned, error=0). Fixed by fix.diff (zone/length/containment guard in hammer_btree_extract + copyout clamp in hammer_ioc_scan_pseudofs): rebuilt kernel #3 returns clean EIO (bad record data 292 @ 90000000ffffffff on console) with the guest staying up, while normal filesystem operation is unaffected. The primitive is an uncontrolled kernel-mode read walk (panic DoS); there is no attacker-controlled write at this sink, so an escalation chain does not exist for this bug - the copyout leak variant is blocked by the CRC gate (returns EIO before copying when CRC mismatches), documented in VERDICT.md.", "exploit_chain": "", "evidence": [ "panic.txt: stock-kernel Fatal trap 12 at calculate_crc32c+0xf0, current process = trigger_scan", "dump_stack.txt: crash-dump backtrace through hammer_ioc_scan_pseudofs:360 and live kgdb inspection of the forged leaf (data_offset garbage, cursor->data wild, error=0)", "run.log: baseline panic (guest wedged) vs fixed-kernel ioctl returned -1 errno=5 twice, guest up", "env.txt: dmesg hammer_btree_extract: bad record data 292 @ 90000000ffffffff on the fixed kernel", "fix.diff + build.log: nativekernel build/install of kernel #3" ], "kernel_refs": [ "sys/vfs/hammer/hammer_pfs.c:358", "sys/vfs/hammer/hammer_pfs.c:363", "sys/vfs/hammer/hammer_btree.c:736", "sys/vfs/hammer/hammer_btree.c:765", "sys/vfs/hammer/hammer_ondisk.c:1153", "sys/vfs/hammer/hammer_crc.h:274", "sys/vfs/hammer/hammer_ioctl.c:264" ], "poc_changes": "Patcher initially wrote data_len at the wrong offset (+48 = data_offset low dword); fixed to +56, but the live-tree element it matched still carried the -1 in the data reference (fixed-kernel console shows data_offset low dword 0xFFFFFFFF, data_len 292) - both fields are equally unvalidated in the stock kernel, so the demonstration is unaffected. Also learned mid-validation that the length-only guard was insufficient (kernel #1 still panicked via the wild data_offset) and extended fix.diff to validate zone + 16K-buffer containment; guards zone set matched to hammer_alloc_datas own policy after an over-strict iteration (#2) broke fresh mounts.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (baseline) / #3: Tue Sep 8 04:04:43 UTC 2026 (fix validation) x86_64", "runtime_sec": 7800, "guest_dirty": 0, "build_cmd": "cc -O -I/usr/src/sys/vfs/hammer -o trigger_scan trigger_scan.c && cc -O -o patch_dlen patch_dlen.c && (fix) make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC", "run_cmd": "./patch_dlen h1.img -1; vnconfig -c -S 10g /dev/vn0 h1.img; mount -o ro -t hammer /dev/vn0 /mnt; ./trigger_scan /mnt 1", "code_hash": "f115d4659a043f7d8eb282720350784c3e3199f01c290f67a6a3eae4191225ee", "notes": "Precondition: attacker-influenced HAMMER image mounted (vfs.usermount, removable media, or mirrored stream). Trigger is unprivileged (uid 1001 used). Crash dump recovered via debug.debugger_on_panic=0 + savecore; kgdb backtrace captured live. Guest reset to with-src snapshot after the run.", "recommended_fix": "Validate the on-disk record data reference in hammer_btree_extract (0 < data_len <= HAMMER_BUFSIZE, data zone in {META,LARGE,SMALL}, no 16K-buffer straddle -> EIO) and clamp the scan copyout to sizeof(struct hammer_pseudofs_data)", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #3: Tue Sep 8 04:04:43 UTC 2026 x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "With fix.diff applied, the identical forged image mounts read-only and the identical unprivileged trigger returns EIO (twice) with the guest staying up and bad record data logged; baseline stock kernel panicked at the same point. Fresh uncorrupted filesystems mount and operate normally (validated during guard iteration #2 -> #3).", "fix_evidence": "run.log section [2]; env.txt dmesg line; build.log (nativekernel #3)" } |