Unchecked info_len in crom_init_context enables OOB heap reads up to ~1000 bytes past csrrom[] buffer
Summary
crom_init_context does not validate hdr->info_len keeps root-directory pointer within 1024-byte csrrom[] buffer. CROM_END computed as root_dir+1003 (assuming info_len==4). For any info_len>4 the bound extends up to ~999 bytes past buffer end neutralizing every subsequent bounds check. For info_len=254: root dir starts at csrrom+1020 CROM_END=csrrom+2023 - 999 bytes past 1024-byte buffer. Every entry read by crom_get via crom_search_key at csrrom[256+] fully OOB and check at line 131 passes because CROM_END itself is OOB. Leaked data includes adjacent fw_device fields and neighboring kernel heap. Values flow into reg->key/reg->val used for pointer arithmetic. Can reach userland via crom_parse_text writing to sdev->vendor/sdev->product exposed through SCSI INQUIRY.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2290 Β· 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 | β | 5.1 KB | β raw | |
| README.md | β | 2.3 KB | β raw | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 6.1 KB | view raw |
DF-2290 β unchecked info_len in crom_init_context β OOB heap reads past csrrom[]
Status: REPRODUCED (verbatim-code rig) β impact class: kernel info leak (OOB read).
Fix: A/B-validated in the same rig (fix.diff hunk 1).
What this pack contains
| file | role |
|---|---|
fwcrom_rig.c |
verbatim-code rig: drives the UNMODIFIED production parser |
build.sh |
builds fwcrom_rig (baseline, links /usr/src/sys/bus/firewire/fwcrom.c verbatim) and fwcrom_rig_fixed (fix.diff applied to a local copy; sys/ never touched) |
run.sh |
evidence run: df2290_guard, df2290_canary, control Γ |
run.log, run.2.log, run.3.log |
three full untrimmed runs (deterministic, identical results) |
build.log |
full compiler + patch output from the guest |
fix.diff |
git-apply-able unified diff (both DF-2290 and DF-2291 hunks) |
env.txt |
guest uname, cc, sha256 of the guest's kernel source file, no-firewire evidence |
VERDICT.md, manifest.json, verdict.json |
verdict narrative + machine-readable results |
How to reproduce
On a DragonFlyBSD guest with /usr/src present:
./build.sh # links the production parser verbatim (sha256 printed & verified)
./run.sh # 6 cells: 3 modes x {baseline, fix.diff-patched}
Expected output (baseline = unmodified production code)
RIG mode=df2290_guard result=OOB_FAULT_CONFIRMED ... fault address is 3 bytes past the buffer end # SIGSEGV on root entry[0], quadlet [1024,1028) RIG mode=df2290_canary result=OOB_LEAK_CONFIRMED crom_search_key(0x03) returned reg=... (buffer+1052; 28 bytes PAST the 1024-byte buffer) reg->key=0x03 reg->val=0xc0ffee <- value read from memory OUTSIDE the buffer RIG mode=control result=PARSED_INBOUNDS # rig sanity, guard page untouched
With fwcrom_rig_fixed (fix.diff applied): REJECTED_NO_FAULT for both attack modes
(crom_init_context: info_len 254 out of range), control still PARSED_INBOUNDS.
Attacker input (the crafted ROM)
rom[0] = info_len<<24 with info_len = 254; rom[255] = root directory header
(crc_len = 0x100). That is byte-identical to what a hostile FireWire device sends in
its config ROM bus-info block β the kernel stores it host-endian into
fw_device.csrrom[1024/4] (firewire.c:1500).
VERDICT β DF-2290 (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_init_context() (sys/bus/firewire/fwcrom.c:62-94) advances the ROM pointer by
1 + hdr->info_len quadlets (fwcrom.c:84) without validating info_len against the
1024-byte buffer, then anchors CROM_END(cc) = stack[0].dir + MAX_ROM - 1
(fwcrom.c:60, MAX_ROM=1004) β an end computed assuming info_len == 4. With a hostile
device's info_len = 254, the root directory lands at buffer+1020 and CROM_END at
buffer+2023, i.e. ~999 bytes past the object, so every subsequent "bounds" comparison
(fwcrom.c:131, 134, 200, 207) is against an out-of-bounds address and is satisfied by
fully out-of-bounds reads.
How it was verified
Verbatim rig (fwcrom_rig.c) links the guest's UNMODIFIED production parser
/usr/src/sys/bus/firewire/fwcrom.c β sha256
b38ce9b52a249f594b4f11e0914eaa7fb6fbbccb0e174611d6a97f875f1466c5, byte-identical to
the audited workspace copy β compiled userspace exactly as usr.sbin/fwcontrol/Makefile
does (-I/usr/src/sys, !_KERNEL). The 1024-byte "csrrom" object is placed at the end
of an mmap'd page; the following page is either PROT_NONE (guard) or canary-filled.
- df2290_guard β ROM with
info_len=254, root dircrc_len=0x100. First entry deref (crom_search_key()βreg->key, fwcrom.c:152-153 via crom_get fwcrom.c:102) faulted atbuffer_end+3(the key byte of root entry[0], whose quadlet occupies [1024,1028) β entirely outside the object). Run 3/3 identical. β the OOB read is real, immediate, and unconditional once the hostile ROM is parsed. - df2290_canary β same ROM, canary page carrying fake immediate entries; the
searched key (CSRKEY_VENDOR) exists ONLY past the buffer.
crom_search_key()walked 8 out-of-bounds entries (the fwcrom.c:130-131 check passed for every one of them, because CROM_END itself is OOB) and returnedregat buffer+1052 withreg->val = 0x00C0FFEEβ parser results sourced from memory the buffer does not own. β in the kernel,reg->key/reg->valfrom adjacent heap flow into fw_device/sbp state (sbp.c:412-421, 567-568, 602-621). - control β well-formed in-bounds ROM parses correctly with the guard page untouched β the faults above are caused by the hostile ROM, not the rig.
Production context (source-cited, not executable on this guest): the ROM arrives from a
remote FireWire device during bus exploration and is stored host-endian in
fw_device.csrrom[1024/4] (firewirereg.h:52-54; firewire.c:1496-1500), inside the
kmalloc'd struct fw_device (firewire.c:1311). OOB reads therefore run into adjacent
kernel heap; values/strings reach userland via sbp's sdev->vendor/sdev->product
filled by crom_parse_text() (sbp.c:606,621) and exposed through SCSI INQUIRY on the
sbp LUN. Read-only bug β leak/DoS ceiling, not corruption: CVSS AV:P (physical
FireWire attach or hostile device on the bus) as filed. Medium/kernleak stands.
Fix A/B (same rig, fix.diff hunk 1)
Baseline reproduces (above). With hunk 1 applied (reject info_len > 4 β the bus-info
block cannot exceed 4 quadlets in a 1024-byte CSR ROM, making CROM_END math sound):
crom_init_context: info_len 254 out of range, depth=-1, zero OOB access in both
attack modes; control unchanged (PARSED_INBOUNDS). No regression.
info_len < 4 remains accepted and merely makes CROM_END conservative β safe.
Honesty notes
- Userspace rig: proves the production parser's reads exceed the 1024-byte object (guard SIGSEGV) and that adjacent-memory contents are returned in parser results (canary). The kernel-side path (kmalloc adjacency, INQUIRY exposure) is source-cited (firewire.c:1311,1500; sbp.c:602-621), not executed β no FireWire hardware exists on the audit guest (env.txt), which is why the verbatim-rig evidence class was used.
- Incidental observation (NOT part of this finding, for the orchestrator): with a
valid info_len,
crom_next()still setsptr->dir = reg + reg->valfor directory-type entries without any range check (fwcrom.c:122) and dereferencesptr->dir->crc_len(fwcrom.c:130) BEFORE the CROM_END comparison β a directory entry with a large 24-bitvalyields an OOB read up to ~64MB past the buffer even after DF-2290's fix. Recommend a follow-up finding. crom_desc()(userspace-only path, fwcontrol) inherits the same neutralized bounds.
Bottom line
Confirmed as filed: unvalidated info_len moves the parser's entire bound past the
buffer, enabling OOB heap reads up to ~999 bytes past fw_device.csrrom with contents
flowing into user-visible SCSI strings. Fix is two lines of validation and is validated
A/B in this rig.
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 and the canary leak; with fix.diff hunk 1 applied, crom_init_context rejects info_len=254 (depth=-1) and BOTH attack modes make zero out-of-bounds accesses, while the in-bounds control ROM still parses identically (no regression). Kernel rebuild not meaningful for this target: the parser is not reachable on the guest (no FireWire hardware/KLD) and the rig compiles the identical production source byte-for-byte.
run.log PATCHED section: 'crom_init_context: info_len 254 out of range' + 'RIG mode=df2290_guard result=REJECTED_NO_FAULT' + 'RIG mode=df2290_canary result=REJECTED_NO_FAULT' + 'RIG mode=control result=PARSED_INBOUNDS'; build.log: 'Hunk #1 succeeded at 77.'
Confirmed kernel references
- sys/bus/firewire/fwcrom.c:60
- sys/bus/firewire/fwcrom.c:84
- sys/bus/firewire/fwcrom.c:102
- sys/bus/firewire/fwcrom.c:130-131
- sys/bus/firewire/iec13213.h:133-147
- sys/bus/firewire/firewirereg.h:52-54
- sys/bus/firewire/firewire.c:1311
- sys/bus/firewire/firewire.c:1496-1500
- sys/dev/disk/sbp/sbp.c:412
- sys/dev/disk/sbp/sbp.c:602-621
Detail
Exploit chain
hostile FireWire device -> config ROM quadlets stored host-endian into fw_device.csrrom[1024/4] during bus exploration (firewire.c:1496-1500) -> crom_init_context advances root dir by unvalidated info_len=254 (fwcrom.c:84), CROM_END lands ~999B past the buffer (fwcrom.c:60) -> crom_search_key/crom_get dereference root entries at buffer+1024.. (fwcrom.c:102,152) with every bounds check neutralized (fwcrom.c:131) -> OOB heap values become reg->key/reg->val consumed by sbp target construction (sbp.c:412-421,567-568,602-621) and crom_parse_text copies OOB bytes into sdev->vendor/product -> local user reads them via SCSI INQUIRY on the sbp LUN (sbp.c:1527-1528). Rig proves the parser-level primitive + content flow; kernel adjacency and INQUIRY legs are source-cited (not executable without FireWire hardware).
Evidence (decisive lines)
run.log (all 3 runs identical): 'RIG mode=df2290_guard result=OOB_FAULT_CONFIRMED ... fault address is 3 bytes past the buffer end' and 'RIG mode=df2290_canary result=OOB_LEAK_CONFIRMED ... crom_search_key(0x03) returned reg=...(buffer+1052; 28 bytes PAST...) reg->val=0xc0ffee'; PATCHED cells: 'crom_init_context: info_len 254 out of range ... REJECTED_NO_FAULT'; control cells PARSED_INBOUNDS on both binaries. build.log shows the verbatim sha256 and clean gcc 8.3 build of the unmodified production file.
PoC changes
No seed pack existed (poc_path was empty); the rig was authored from scratch in verify mode. Iterations: (1) added
Verified recommended fix
crom_init_context: reject hdr->info_len > 4 (bus-info block cannot exceed 4 quadlets in a 1024-byte CSR ROM) before computing the root-directory pointer -- see fix.diff hunk 1.
Verdict
REPRODUCED via verbatim-code rig on the QEMU guest (no FireWire hardware/KLD exists there, so production-handler dispatch is impossible; evidence class chosen accordingly). The rig links the UNMODIFIED production parser /usr/src/sys/bus/firewire/fwcrom.c (sha256 b38ce9b5..., byte-identical to the audited tree) compiled userspace exactly as usr.sbin/fwcontrol builds it, and drives it with a hostile device's config ROM against a 1024-byte object placed at a page boundary. info_len=254 moves the root directory to buffer+1020 and CROM_END to buffer+2023 (~999 bytes past the object): the first root-entry deref faulted on a PROT_NONE guard page at buffer_end+3 (quadlet [1024,1028) fully OOB), and with a canary page crom_search_key() walked 8 out-of-bounds entries -- the fwcrom.c:130-131 check passed every time because CROM_END itself is OOB -- returning reg->val=0x00C0FFEE planted only past the buffer. In production the same reads run into the kmalloc'd struct fw_device (firewire.c:1311) past csrrom[1024/4] (firewirereg.h:52-54, filled host-endian at firewire.c:1500), and the OOB-sourced values/strings reach userland via sbp.c:602-621 (sdev->vendor/product) into SCSI INQUIRY (sbp.c:1527-1528). Read-only primitive: leak ceiling, consistent with the filed Medium/kernleak classification. fix.diff hunk 1 (reject info_len>4) A/B-validated in the same rig: attack modes rejected with zero OOB access, well-formed ROM unregressed.
No comments yet.