crom_parse_text bounds check missing sizeof(u_int32_t) multiplier allows OOB heap read past ROM buffer
Summary
crom_parse_text checks (vm_offset_t)textleaf + textleaf->crc_len > CROM_END(cc) but crc_len is quadlet count not byte count - check missing * sizeof(u_int32_t) factor and is 4x too permissive. Text leaf near buffer end passes check but text data reads up to hundreds of bytes past ROM buffer leaking kernel heap into user-visible SCSI inquiry strings. For leaf at buffer+1000 with crc_len=10: check passes (1000+10=1010<=1023) but textleaf->text[0..7] reads buffer+1012 through buffer+1043 up to 19 bytes past buffer. Works even with default info_len=4.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2291 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fwcrom_rig.c | β | 11.6 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| run.log | β | 2.6 KB | view raw | |
| run.2.log | β | 2.6 KB | view raw | |
| run.3.log | β | 2.6 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| env.txt | β | 2.0 KB | view raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| README.md | β | 1.9 KB | β raw | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 5.8 KB | view raw |
DF-2291 β crom_parse_text bounds check missing sizeof(u_int32_t) β OOB read past ROM buffer
Status: REPRODUCED (verbatim-code rig) β impact class: kernel info leak (OOB read),
independent of DF-2290 (triggers with the default info_len = 4).
Fix: A/B-validated in the same rig (fix.diff hunk 2).
What this pack contains
Same layout as the DF-2290 pack (fwcrom_rig.c, build.sh, run.sh, full logs,
fix.diff, env.txt). The rig and build scripts are identical; run.sh drives the
df2291_* modes.
How to reproduce
./build.sh
./run.sh
Expected output (baseline = unmodified production code)
RIG mode=df2291_guard result=OOB_FAULT_CONFIRMED
... fault address is 0 bytes past the buffer end # SIGSEGV reading textleaf->text[3] at byte 1024
RIG mode=df2291_canary result=OOB_LEAK_CONFIRMED
crom_parse_text output (the buffer that becomes sdev->vendor/product, sbp.c:606,621):
ab0de000 ab0de001 ab0de002 c0ffee00 c0ffee01 c0ffee02 c0ffee03 c0ffee04
words 3..7 = canary 0xC0FFEE00..04 read from bytes 1024..1043, PAST the 1024-byte buffer
RIG mode=control result=PARSED_INBOUNDS # rig sanity
With fwcrom_rig_fixed: NULLSTR_NO_FAULT (leaf rejected by bounds check: out="(null)")
for both attack modes; control still PARSED_INBOUNDS (no regression).
Attacker input (the crafted ROM)
Default bus-info block (info_len = 4), root directory at byte 20 with one entry
{key=CROM_TEXTLEAF(0x81), val=244} pointing at a text leaf placed at byte 1000 whose
crc_len = 10 quadlets. The buggy check at fwcrom.c:207 tests 1000 + 10 <= 1023
(passes) but the read loop then consumes text[0..7] = bytes 1012..1043 β 20 bytes past
the 1024-byte buffer. The 5 canary words that land in the output string are the proof
that adjacent-kernel-heap contents flow into the buffer that becomes the SCSI INQUIRY
vendor/product string in production (sbp.c:606,621).
VERDICT β DF-2291 (verify mode)
status: reproduced Β· reproduced=1 Β· impact=leak Β· confidence=certain rig: verbatim-code (guest has no FireWire hardware/KLD β see env.txt)
Claim under test
crom_parse_text() (sys/bus/firewire/fwcrom.c:187-225) validates the text leaf with
if ((vm_offset_t)textleaf + textleaf->crc_len > CROM_END(cc)) /* fwcrom.c:207 */
but crc_len is a QUADLET count (see crom_add_simple_text, fwcrom.c:459:
tl->crc_len = howmany(sizeof(struct csrtext) + len, sizeof(u_int32_t))), while the
read loop consumes (crc_len - 2) quadlets of textleaf->text[] (fwcrom.c:215-219),
i.e. a byte extent of 12 + (crc_len-2)*4 = 4*crc_len + 4. The check is therefore ~4x
too permissive; a leaf near the buffer end passes validation and the loop reads past the
1024-byte ROM with the default info_len = 4 β no dependency on DF-2290.
How it was verified
Same verbatim rig as DF-2290 (production parser linked unmodified; sha256 in build.log).
- df2291_guard β ROM:
info_len=4, root dir @20 with one entry{key=0x81 CROM_TEXTLEAF, val=244}β leaf at byte 1000,crc_len=10. Buggy check:1000 + 10 <= CROM_END(1023)passes. Loop reads text[0..2] (bytes 1012..1023, in-bounds) then faults reading text[3] at exactly buffer_end+0 (SIGSEGV, si_addr == rom+1024). Run 3/3 identical. β the read crosses the object boundary by up to 20 bytes (last byte at 1043). - df2291_canary β same ROM, canary page after the buffer. Output of
crom_parse_text(the exact buffer that in production issdev->vendor/sdev->product, sbp.c:606,621):ab0de000 ab0de001 ab0de002 c0ffee00 c0ffee01 c0ffee02 c0ffee03 c0ffee04β words 0..2 are the in-buffer markers, words 3..7 are canary read from bytes 1024..1043, i.e. adjacent-memory contents copied into the user-visible string. The leak primitive is demonstrated end-to-end at parser level. - control β in-bounds leaf (byte 512, crc_len=6) parses correctly, guard page untouched β rig sanity.
Production context: hostile FireWire device's config ROM β fw_device.csrrom[1024/4]
(firewirereg.h:52-54, filled at firewire.c:1500 host-endian) β sbp target attach calls
crom_parse_text(cc, sdev->vendor, sizeof(sdev->vendor)) and ..._product
(sbp.c:602-621) β SCSI INQUIRY data readable by any local user with access to the sbp
LUN. Ceiling: the copy is clamped by the destination (len), and in the kernel path
len = sizeof(sdev->vendor) = 32 (sbp.c:187,606) β up to 32 bytes of adjacent kernel
heap per text leaf reach the INQUIRY string (a leaf near CROM_END makes ALL 32 bytes
out-of-bounds-sourced; the read fault extent reaches ~43 bytes past the buffer).
crom_parse_text is called for both vendor and product (and the revision loop walks
more leaves), and a hostile ROM may contain many text leaves β each swept with the
broken bound. The userspace fwcontrol/crom_desc path passes larger len, reading
further. Read-only bug β leak class; Medium/kernleak as filed stands.
Fix A/B (same rig, fix.diff hunk 2)
Baseline reproduces (above). With hunk 2 applied (bound the leaf's full byte extent:
textleaf + (crc_len + 1) * sizeof(u_int32_t) > CROM_END): both attack modes return
the safe "(null)" string, no fault, no canary; control still parses
(PARSED_INBOUNDS) β the stricter check accepts every legitimately-placed leaf because
crom_load() emits leaves whose quadlet extent is crc_len (fwcrom.c:459,521-523).
The +1 term covers the loop's 4*crc_len + 4 reach exactly (one byte conservative
in the boundary case, which is unreachable for well-formed leaves).
Honesty notes
- Same userspace-rig caveat as DF-2290: kernel adjacency + INQUIRY leg is source-cited, not executed (no FireWire hardware on the audit guest).
- The finding's arithmetic ("reads buffer+1012 through buffer+1043, up to 19 bytes
past") is confirmed modulo the exact struct size:
struct csrtextheader is 12 bytes (BIT16x2 + spec + lang, iec13213.h:148-159), so text starts at leaf+12; for a leaf at 1000 the read extent is 1012..1043 β 20 bytes past the 1024-byte buffer end. Immaterial to the verdict. - Interaction: DF-2290's fix is NOT required for this one (default info_len), but with both unfixed a hostile ROM can chain them (DF-2290 to widen CROM_END, DF-2291 to sweep larger windows past the buffer).
Bottom line
Confirmed as filed: the crc_len-as-bytes check at fwcrom.c:207 lets a hostile text
leaf read past fw_device.csrrom (with DF-2290 chained, far past it), with the leaked
bytes landing in SCSI INQUIRY strings. One-line fix, A/B validated, no regression.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedA/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 loop's 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.
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.'
Confirmed kernel references
Detail
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 kmalloc'd 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 (decisive lines)
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.
PoC changes
No seed pack existed (poc_path was empty); rig authored from scratch in verify mode. Iterations: (1) added
Verified recommended fix
crom_parse_text: compare the leaf's 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.
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); fwcontrol's 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.
No comments yet.