DF-5497 / verdict.json
{ "finding_id": "DF-5497", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the QEMU guest with the production sys/bus/firewire/fwcrom.c compiled unmodified into a KLD harness (DF-5302/5321/5413 pattern; FireWire is hardware-gated in QEMU, so the harness feeds the production parser a device-controlled 1024-byte Config ROM exactly as fw_bus_explore() fills fwdev->csrrom[] and sbp(4) walks it). crom_next() derives the nested-directory pointer from the unvalidated 24-bit entry value (fwcrom.c:122, reg+reg->val, up to +64 MB forward) and loads ptr->dir->crc_len (fwcrom.c:130/134) BEFORE the CROM_END bound check on the next operand of the &&. Demonstrated: (1) userspace guard-page model -- the production parser faults at rom+1226 (200 bytes past the 1024-byte ROM, predicted window 1224..1227 for the crc_len bitfield load) and at rom+0x4000014 (full 24-bit reach), while the in-ROM control walks cleanly -- proving the load precedes the check that would have rejected the directory; (2) in-kernel, with the slab around the model ROM groomed with a crc_len=1 pattern, four crom_next: bound check failed oracle lines fired -- production reads of kernel heap memory outside the ROM that observably steer the walk; (3) a +4..+64 MB descent sweep panics the kernel: panic: assertion obj != NULL failed in vm_object_hold_shared, trap 000000000000000c, RIP dfh_crom_next+0x53 (the crc_len load), guest down (panic.txt). Read-only primitive (a wild directory can never be entered, so no OOB write from this defect); attacker is any device on the FireWire bus via sbp.c:412/468/567/602 crom_init_context/crom_search_key walks. Distinct from the six known findings (DF-1083/1084/1085/1086, DF-2289/2290/2291): those bound the damage to ~1 KB past the buffer or the root/text-leaf paths; this is the nested-directory dereference-before-validation with a 64 MB reach. Fix validated: with fix.diff (bounds-check the derived directory pointer before descending) applied to guest /usr/src, userspace subtests walk cleanly, oracle lines 4->0, the +4..64 MB sweep completes with no panic and the guest stays up.", "exploit_chain": "attacker: malicious/uncompromised FireWire peer device on the bus (unauthenticated, physical/prox access) -> bus reset: fw_bus_explore() fetches its Config ROM into fwdev->csrrom[256] (firewire.c:1500, firewirereg.h:52-54) -> sbp(4) probe walks it: crom_init_context + crom_search_key -> crom_next (sbp.c:412-425) -> ROM root directory contains a CSRTYPE_D entry (key 0xd1) with val=V -> kernel loads the 16-bit crc_len at csrrom+24+4*V without any prior bound -> mapped: silent foreign-memory read + 1-bit console oracle (bound check failed) per crafted V; unmapped: Fatal trap 12 in crom_next -> kernel panic (deterministic DoS). uid=0 chain not applicable: read-only primitive (wild directories are never entered), hardware-gated trigger.", "evidence": "panic.txt (serial console: trap 000000000000000c, RIP dfh_crom_next+0x53, four oracle lines before the fault); run.log (userspace guard-page model: predicted-address SIGSEGVs at rom+1226 and rom+0x4000014, clean control; KLD mode 1 groomed oracle 4x; mode 2 never returns); fix_run.log (patched: subtests clean, oracle 4->0, mode 2 survives, guest up); fix.diff; build.log/fix_build.log; VERDICT.md for the full path:line narrative", "kernel_refs": [ "sys/bus/firewire/fwcrom.c:122", "sys/bus/firewire/fwcrom.c:130", "sys/bus/firewire/fwcrom.c:134", "sys/bus/firewire/iec13213.h:124", "sys/bus/firewire/iec13213.h:129", "sys/bus/firewire/firewirereg.h:52", "sys/bus/firewire/firewirereg.h:54", "sys/bus/firewire/firewire.c:1500", "sys/dev/disk/sbp/sbp.c:412", "sys/dev/disk/sbp/sbp.c:416" ], "poc_changes": "Harness written fresh for this file (DF-5302/DF-5321 KLD pattern: production fwcrom.c #included with -D-renamed globals; the only synthetic state is the device-controlled ROM buffer and its slab neighbourhood). Bring-up fixes: (1) re-added the fire-flag variable removed with a duplicate sysctl; (2) sys/queue.h include for the -DTEST userspace build; (3) userspace model fault prediction widened to a 4-byte window because GCC loads the crc_len bitfield as a halfword at dir+2; (4) mode-1 oracle made observable with a slab groom (64 same-class blocks patterned crc_len=1, every other freed, ROM allocated into a hole) after the first run read all-zero neighbours; (5) mode-2 probe 16 wraps to val=0 (16*0x100000 & 0xffffff), which descends into the ROM itself and produces one bound-check line against the known CROM_END slack (DF-2290) -- the +64MB probes are entries 0..15 steps 1..15.", "attempts": 4, "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": 1020, "guest_dirty": 0, "build_cmd": "sh build.sh (cd /root/df5497 && make -> df5497.ko with production fwcrom.c; cc -I/usr/src/sys -o c5497_user c5497_user.c /usr/src/sys/bus/firewire/fwcrom.c)", "run_cmd": "./c5497_user; kldload ./df5497.ko; sysctl kern.df5497_mode=1; sysctl kern.df5497_fire=1; sysctl kern.df5497_mode=2; sysctl kern.df5497_fire=1 (mode 2 expects the panic)", "code_hash": "9e9313b271891f73e76619b76c0ef9f0b71f54b007224c0e901e5687c2d7b238", "notes": "Baseline panic captured on the serial console after ssh died and vm.sh status => down (honest-status table: reproduced/panic). The remaining single bound check failed line in the PATCHED mode-2 run is the val-wrapped in-ROM descent walking against the pre-existing CROM_END unit slack tracked by DF-2290 (reads bounded ~1 KB past the ROM); the wild-pointer dereference this finding covers is fully eliminated. The walk-call counter (64) exceeds the 16 entries because in-ROM bogus directories with huge crc_len values are traversed entry-by-entry until the CROM_END check stops them; every such entry read stays inside the known slack window.", "recommended_fix": "In crom_next(), bounds-check the derived directory pointer ((vm_offset_t)dir <= CROM_END(cc)+1-sizeof(*dir)) before assigning ptr->dir and dereferencing crc_len; skip the entry as a leaf otherwise (fix.diff).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Baseline: userspace faults at the two predicted OOB addresses, 4 in-kernel oracle lines, mode-2 panic (guest down). Patched (fix.diff on /usr/src, module + model rebuilt, identical triggers): userspace subtests A/B walk cleanly, oracle lines 0, mode-2 sweep completes with no panic and the guest stays up.", "fix_evidence": "fix_run.log, fix_build.log, fix.diff, /tmp transcript of the patched userspace model (clean walks)" } |