β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2290

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2290 Β· 12 files
FileTypeDescriptionSize
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.md
↓ download raw

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.

  1. df2290_guard β€” ROM with info_len=254, root dir crc_len=0x100. First entry deref (crom_search_key() β†’ reg->key, fwcrom.c:152-153 via crom_get fwcrom.c:102) faulted at buffer_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.
  2. 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 returned reg at buffer+1052 with reg->val = 0x00C0FFEE β€” parser results sourced from memory the buffer does not own. β†’ in the kernel, reg->key/reg->val from adjacent heap flow into fw_device/sbp state (sbp.c:412-421, 567-568, 602-621).
  3. 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 sets ptr->dir = reg + reg->val for directory-type entries without any range check (fwcrom.c:122) and dereferences ptr->dir->crc_len (fwcrom.c:130) BEFORE the CROM_END comparison β€” a directory entry with a large 24-bit val yields 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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/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.'
↓ fix.diffper-fix-DF-2290

Confirmed kernel references

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 for ptrdiff_t under gcc 8.3; (2) regenerated fix.diff mechanically with diff -u after the guest's GNU patch 2.0 rejected the initially hand-counted hunks; (3) NULLSTR classification moved so the fixed binary's '(null)' outcome is reported as the expected fixed behavior. Trigger values match the finding exactly (info_len=254, root crc_len=0x100).

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.