crom_next() dereferences the peer-derived nested-directory pointer (reg + 24-bit val, forward up to +64 MB) before any bounds check β OOB kernel read and deterministic kernel panic from a malicious FireWire device's Config ROM
Summary
On CSRTYPE_D entries the sub-directory pointer is computed from the entry's 24-bit value and immediately loaded (&& evaluates left-first) BEFORE the CROM_END bound check - never validated at all: a peer controls the whole 1024-byte ROM and can aim the load at csrrom+24+4*V for any V up to 0xffffff (+64 MB). Mapped target = silent foreign-memory read + a 1-bit oracle per probe (the bound-check console line iff the foreign word is nonzero); unmapped = Fatal trap 12. Distinct from all 6 knowns (root-ptr ~1KB / text-leaf / stack-write families - none covers the nested-directory 64MB reach). Production-code KLD + guard-page userspace model: V=300 faults at the exact predicted load window, V=0xffffff at +64MB, slab-groom oracle lines 4x, in-kernel +4..64MB sweep -> panic at dfh_crom_next+0x53; bounds-check-before-assign fix validated (no fault, oracle 0, no panic). Read-only primitive - no OOB write arises.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5497 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 5.8 KB | β raw | |
| verdict.json | β | 6.6 KB | view raw | |
| df5497.c | β | 6.5 KB | view raw | |
| Makefile | β | 702 B | β download | |
| c5497_user.c | β | 4.1 KB | view raw | |
| build.sh | β | 144 B | view raw | |
| run.sh | β | 222 B | view raw | |
| build.log | β | 5.9 KB | view raw | |
| fix_build.log | β | 5.9 KB | view raw | |
| run.log | β | 4.4 KB | view raw | |
| panic.txt | β | 1.0 KB | view raw | |
| fix_run.log | β | 3.5 KB | view raw | |
| env.txt | β | 280 B | view raw | |
| fix.diff | β | 643 B | view raw |
DF-5497 β crom_next() dereferences the device-derived nested-directory pointer before any bounds check (OOB kernel read up to +64 MB; kernel panic)
File: sys/bus/firewire/fwcrom.c β pass 2 (GLM 5.3 re-audit).
Build (guest, root; requires /usr/src, i.e. vm.sh reset with-src)
sh build.sh # KLD df5497.ko (production fwcrom.c #included, symbols
# renamed via -D) + userspace guard-page model c5497_user
sh /root/df5498/build.sh # (sibling model, see DF-5498 pack)
Run
./c5497_user # userspace model: production parser vs guard-page ROM kldload ./df5497.ko sysctl kern.df5497_mode=1 # oracle: OOB reads +1.2..16 KB past a kmalloc(1024) ROM sysctl kern.df5497_fire=1 sysctl kern.df5497_mode=2 # sweep: descents +4..64 MB -> EXPECT KERNEL PANIC sysctl kern.df5497_fire=1
Expected (baseline, unpatched /usr/src)
- userspace model: subtest A (V=300) SIGSEGV at the predicted
rom+1224..1227load (200 bytes into the guard page past the 1024-byte ROM β the load happens BEFORE theCROM_ENDcomparison that would have rejected the directory); subtest B (V=0xffffff) SIGSEGV atrom+0x4000014(the full 24-bit, +64 MB reach); control C (V=100, in-ROM directory) walks cleanly. - KLD mode 1:
crom_next: bound check failedoracle lines β the production parser readcrc_lenout of groomed heap neighbours outside the ROM. - KLD mode 2:
Fatal trap 12/panicwith RIP insidecrom_next(observed:dfh_crom_next+0x53), guest goes down β seepanic.txt.
Fix validation
fix.diff applied inside the guest /usr/src, module and model rebuilt:
subtests A/B walk cleanly (no fault), oracle lines 4 -> 0, mode 2 completes
with no panic and the guest stays up (fix_run.log).
Hardware-gated entry (no FireWire controller in QEMU): 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.
DF-5497 VERDICT β REPRODUCED (kernel panic via production crom_next(); OOB read up to +64 MB from a device-controlled ROM)
Root cause
crom_next() walks Config-ROM directories supplied by the peer FireWire
device (buffered byte-swapped in fwdev->csrrom[CSRROMSIZE/4], a 1024-byte
heap array β firewirereg.h:52-54, filled quadlet-by-quadlet from the wire at
firewire.c:1500). When an entry has key type CSRTYPE_D (every unit
directory, key 0xd1, qualifies), it derives the sub-directory pointer
straight from the entry's 24-bit value:
sys/bus/firewire/fwcrom.c:122βptr->dir = (struct csrdirectory *)(reg + reg->val);
reg->val is u_int32_t val:24 (iec13213.h:124-132), so reg + reg->val
is an unvalidated forward pointer up to 0xffffff quadlets = +64 MB from the
entry. The very next statement dereferences it:
sys/bus/firewire/fwcrom.c:130βif (ptr->index < ptr->dir->crc_len && (vm_offset_t)crom_get(cc) <= CROM_END(cc))β the left operand loads the 16-bitcrc_lenbitfield before theCROM_ENDbound check on the right operand is even evaluated (C&&evaluates left first);sys/bus/firewire/fwcrom.c:134β second unguardedptr->dir->crc_lenload.
Contrast: the root directory pointer is (incorrectly, DF-1084/DF-2290)
derived in crom_init_context() and text-leaf pointers get a start-of-leaf
check in crom_parse_text() (fwcrom.c:200-201); nested directory pointers
get no check at all before the load β this is a distinct defect from the six
known findings (DF-1083/1084/1085/1086, DF-2289/2290/2291), which bound the
damage to ~1 KB past the buffer and to the root/text paths.
Reachability (in-kernel, production call chain)
A peer node's Config ROM is fetched on bus reset and every sbp(4) probe walks
it with this exact code: sbp.c:412/468/567/602 (crom_init_context(&cc,
target->fwdev->csrrom)) β crom_search_key() (fwcrom.c:146-158) β
crom_next(). A malicious device puts one CSRTYPE_D entry with a large
value in its root directory; nothing else is required. FireWire is
unauthenticated β any device on the bus (physical/prox access, or a hostile
SBP-2 target) is the attacker. Hardware-gated for the entry; the QEMU guest
has no FireWire controller, so verification used the established
production-code KLD harness + userspace-model pattern (DF-5302/DF-5321/DF-5413
precedents), driving the unmodified production parser.
Reproduction
- Userspace model (
c5497_user.c, productionfwcrom.ccompiled for userland asfwcontrol(8)links it, ROM placed against aPROT_NONEguard page): * V=300 β SIGSEGV atrom+1226(predicted load windowrom+1224..1227; GCC emits a halfword load for thecrc_lenbitfield): the load is 200 bytes past the 1024-byte ROM and happens before theCROM_ENDcheck that would have rejected the directory (run.log). * V=0xffffff β SIGSEGV atrom+0x4000014β the full +64 MB reach. * V=100 (in-ROM) β clean walk (control). - KLD oracle mode (production
fwcrom.cindf5497.ko; slab groom: 64 same-class 1024-byte blocks patterned withcrc_len=1, every other one freed, model ROM allocated into a hole): fourcrom_next: bound check failedlines β the production parser readcrc_lenfrom kernel heap memory outside the ROM and that foreign content observably steers the walk (1-bit oracle per crafted value; attacker-selected forward offset, 4-byte granularity). - KLD panic mode (16 descents, +4 MB..+64 MB steps): mapped probes print
oracle lines; the first unmapped one panics the kernel β
panic: assertion "obj != NULL" failed in vm_object_hold_shared,trap 000000000000000c, RIPdfh_crom_next+0x53(thecrc_lenload), guest down (panic.txt, serial console).
Impact
- Kernel panic / local-physical DoS (deterministic: probe an unmapped kernel_map offset) β demonstrated.
- OOB kernel-memory read: 4 bytes at a device-chosen forward offset up to +64 MB from a heap object, with a 1-bit content oracle (the console line) per probe β repeatable at attacker pace across walks.
- Read-only primitive: a wild directory can never be entered (entering
requires
dir+entry[0] <= CROM_END, i.e. near-ROM), so no OOB write arises from this defect alone.
Severity: Medium (hardware-gated physical-bus attacker; family calibration with DF-2290/DF-2291 = Medium kernleak; this primitive is strictly stronger: +64 MB reach and panic-capable). CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H.
Fix validation
fix.diff (validate the derived directory pointer against CROM_END before
descending/dereferencing) applied to the guest's /usr/src, module and model
rebuilt:
- userspace model: A/B walk cleanly, no fault (exit 0);
- KLD mode 1: oracle lines 4 β 0 (no OOB neighbour reads at all);
- KLD mode 2: no panic, walk finishes, guest stays up.
The single remaining bound check failed line in patched mode 2 comes from the
16th probe whose value wraps to 0 (160x100000 & 0xffffff == 0), descending
into* the ROM and walking entries against the pre-existing, separately-tracked
CROM_END unit slack (DF-2290's territory) β reads stay within that known
~1 KB window; the +64 MB primitive is gone.
Artifacts
run.log (all baseline manifestations), panic.txt (serial-console panic),
build.log / fix_build.log, fix_run.log, env.txt, df5497.c,
Makefile, c5497_user.c, build.sh, run.sh, fix.diff.
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
fixedBaseline: 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_run.log, fix_build.log, fix.diff, /tmp transcript of the patched userspace model (clean walks)
Confirmed kernel references
Detail
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 (decisive lines)
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
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.
Verified 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).
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.
No comments yet.