DF-2291 / verdict.json
{ "finding_id": "DF-2291", "status": "reproduced", "verdict": "REPRODUCED via verbatim-code rig on the QEMU guest (no FireWire hardware/KLD; verbatim-rig evidence class). The rig links the UNMODIFIED production parser /usr/src/sys/bus/firewire/fwcrom.c (sha256 b38ce9b5..., byte-identical to the audited tree) and parses a hostile ROM with the DEFAULT info_len=4 (independent of DF-2290): root directory at byte 20 with one text-leaf entry pointing at byte 1000, crc_len=10 quadlets. The check at fwcrom.c:207 compares 1000+10<=1023 (passes) because crc_len is treated as bytes although it is a quadlet count (crom_add_simple_text, fwcrom.c:459); the loop at fwcrom.c:215-219 then reads text[0..7] = bytes 1012..1043. Guard-page run: SIGSEGV at exactly buffer_end+0 (text[3] read at byte 1024). Canary run: the crom_parse_text output -- the exact buffer that becomes sdev->vendor/product (sbp.c:606,621) -- contains words c0ffee00..c0ffee04 read from bytes 1024..1043 past the 1024-byte object, i.e. adjacent-memory contents are copied into the user-visible string. In production the copy is clamped by sizeof(sdev->vendor)=32 (sbp.c:187) and lands in SCSI INQUIRY output (sbp.c:1527-1528); fwcontrols userspace crom_desc path passes larger len and reads further. Read-only primitive: leak ceiling, consistent with Medium/kernleak. fix.diff hunk 2 (bound (crc_len+1)*4 bytes) A/B-validated: leaf rejected to (null), zero OOB, no regression.", "reproduced": true, "impact": "leak", "confidence": "certain", "exploit_chain": "hostile FireWire device -> config ROM stored host-endian in fw_device.csrrom[1024/4] (firewire.c:1500, firewirereg.h:52-54) -> sbp target attach: crom_search_key finds the CROM_TEXTLEAF entry (sbp.c:602-605) -> crom_parse_text(cc, sdev->vendor, 32) passes the quadlet-count-as-bytes check at fwcrom.c:207 and copies up to 32 bytes starting at leaf+12 (fwcrom.c:214-219) -> a leaf placed at bytes ~1000-1023 sources those bytes from past the buffer (kernel heap adjacent to the kmallocd fw_device, firewire.c:1311) -> bcopy into the SCSI INQUIRY response (sbp.c:1527-1528) readable by a local user. Rig proves the parser-level read past the object and the content flow into the string buffer; kernel adjacency and INQUIRY legs are source-cited (no FireWire hardware on the audit guest).", "evidence": "run.log (all 3 runs identical): RIG mode=df2291_guard result=OOB_FAULT_CONFIRMED ... fault address is 0 bytes past the buffer end and RIG mode=df2291_canary result=OOB_LEAK_CONFIRMED ... ab0de000 ab0de001 ab0de002 c0ffee00 c0ffee01 c0ffee02 c0ffee03 c0ffee04 ... words 3..7 = canary read from bytes 1024..1043, PAST the 1024-byte buffer; PATCHED cells: NULLSTR_NO_FAULT (leaf rejected by bounds check: out=\"(null)\"); control cells PARSED_INBOUNDS on both binaries.", "kernel_refs": [ "sys/bus/firewire/fwcrom.c:207", "sys/bus/firewire/fwcrom.c:215-219", "sys/bus/firewire/fwcrom.c:459", "sys/bus/firewire/iec13213.h:148-159", "sys/bus/firewire/firewirereg.h:52-54", "sys/bus/firewire/firewire.c:1500", "sys/dev/disk/sbp/sbp.c:187", "sys/dev/disk/sbp/sbp.c:602-621", "sys/dev/disk/sbp/sbp.c:1527-1528" ], "poc_changes": "No seed pack existed (poc_path was empty); rig authored from scratch in verify mode. Iterations: (1) added <stddef.h> for ptrdiff_t under gcc 8.3; (2) fix.diff regenerated mechanically after the guests GNU patch 2.0 rejected hand-counted hunks; (3) (null) outcome classified as NULLSTR_NO_FAULT in both guard and canary modes so the fixed binarys behavior reports cleanly. Trigger matches the finding (leaf at 1000, crc_len=10, default info_len=4); struct math corrected: struct csrtext header is 12 bytes, so text spans 1012..1043 (20 bytes past), vs 19 stated in the finding -- immaterial.", "attempts": 3, "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": 420, "guest_dirty": 0, "build_cmd": "cd findings/poc/DF-2291 && ./build.sh # cc -O0 -g -Wall -I/usr/src/sys -o fwcrom_rig fwcrom_rig.c /usr/src/sys/bus/firewire/fwcrom.c ; plus fwcrom_rig_fixed from fix.diff-patched local copy", "run_cmd": "cd findings/poc/DF-2291 && ./run.sh # df2291_guard df2291_canary control x {baseline,patched}", "code_hash": "dc18340a00b31765d8256f263fd8273ebc87616b152fcfb07f8b72e86bf726c5", "notes": "Leak-class finding (CWE-125 OOB read): no uid0 path, primitive is read-only. Independent of DF-2290 (default info_len), but the two chain: DF-2290s widened CROM_END lets a hostile leaf sit far past 1024 with DF-2291 sweeping larger windows. 3 identical runs (run.log, run.2.log, run.3.log). Guest left up and clean (only /tmp/fwverify used, removed).", "recommended_fix": "crom_parse_text: compare the leafs full byte extent -- (vm_offset_t)textleaf + (textleaf->crc_len + 1) * sizeof(u_int32_t) > CROM_END(cc) -- instead of adding the raw quadlet count; see fix.diff hunk 2.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "A/B in the same verbatim rig: baseline reproduces both the guard-page fault (SIGSEGV at buffer_end+0) and the canary leak into the output string; with fix.diff hunk 2 applied, crom_parse_text returns (null) for the hostile leaf in BOTH attack modes with zero out-of-bounds access, and the in-bounds control leaf still parses identically (PARSED_INBOUNDS, no regression -- the +1 term conservatively covers the loops 4*crc_len+4 reach). Kernel rebuild not meaningful: parser unreachable on the guest (no FireWire hardware/KLD); the rig compiles the identical production source byte-for-byte.", "fix_evidence": "run.log PATCHED section: RIG mode=df2291_guard result=NULLSTR_NO_FAULT (leaf rejected by bounds check: out=\"(null)\") + same for df2291_canary + RIG mode=control result=PARSED_INBOUNDS; build.log: Hunk #2 succeeded at 217." } |