Off-by-one in CROM_MAX_DEPTH check in crom_next() allows stack OOB write of 12 bytes
Summary
crom_next at fwcrom.c:115 has if(cc->depth >= CROM_MAX_DEPTH=10) which at depth 9 evaluates 9>=10=false, so cc->depth++ makes it 10, then ptr=&cc->stack[10] is OOB on stack[10] array (valid 0..9, iec13213.h:200,206-209). Lines 122-123 write ptr->dir (8 bytes) and ptr->index (4 bytes) into adjacent caller stack frame. struct crom_context cc is on the kernel stack in sbp_alloc_lun (sbp.c:412) and sbp_probe_lun (sbp.c:602). 10-deep nested directories in a malicious ConfigROM (~80 bytes + 20-byte bus-info, well within 1024-byte CSRROMSIZE) triggers at device-attach. ptr->dir = (struct csrdirectory*)(reg + reg->val) where reg->val is attacker-controlled 24-bit. Any FireWire device on the bus triggers this on host attach via fw_bus_explore -> sbp_alloc_lun -> crom_init_context + crom_next. Fix: cc->depth+1>=CROM_MAX_DEPTH.
Discussion (0)
PoC verification
Evidence pack
findings/poc/1083 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | verbatim crom_next + crafted 10-deep-nested CROM; canary detects &stack[10] OOB write | 9.4 KB | view raw |
| harness_fixed.c | exploit-chain | same harness with the one-line fix applied β proves the guard closes the OOB | 9.4 KB | view raw |
| fix.diff | suggested-fix | git-apply-able: fwcrom.c:115 >= CROM_MAX_DEPTH -> >= CROM_MAX_DEPTH - 1 | 371 B | view raw |
| build.sh | build-script | cc -O0 -g -o harness harness.c | 154 B | view raw |
| run.sh | run-script | ./harness | 101 B | view raw |
| build.log | build-log | harness build output (BUILD_EXIT=0) | 212 B | view raw |
| run.log | run-log | baseline (unfixed) run: BUG CONFIRMED, canary corrupted | 765 B | view raw |
| fix_run.log | run-log | before/after on patched #1 kernel: unfixed=BUG, fixed=canary intact | 1.4 KB | view raw |
| fix_build.log | build-log | full nativekernel build log, rc=0 | 5.6 MB | β download |
| env.txt | environment | uname, cc, GENERIC firewire config, nm symbol addresses | 1.3 KB | view raw |
| README.md | readme | build/run/expected + mechanism summary | 2.0 KB | β raw |
| VERDICT.md | verdict | full narrative: mechanism, reachability, proof, fix validation | 5.5 KB | β raw |
DF-1083 β reproduce
Bug
Off-by-one in the CROM_MAX_DEPTH guard of crom_next(),
sys/bus/firewire/fwcrom.c:115. At cc->depth == 9 the test
9 >= 10 is false, so the guard does NOT fire; depth is incremented
to 10 and &cc->stack[10] (valid indices are 0..9) is written with a
16-byte struct crom_ptr β an out-of-bounds kernel stack write.
Why a harness (no live kernel trigger)
The Configuration-ROM parser is reached only when the kernel attaches a
FireWire device (SBP-2 target). The QEMU audit guest has no FireWire
controller, so the live code path cannot be exercised. Instead the
harness compiles the verbatim crom_init_context/crom_get/crom_next
(fwcrom.c:62-143) and the exact structures (iec13213.h) and feeds them a
crafted 10-deep-nested IEEE-1212 Configuration ROM β the same bytes the
kernel receives from an external FireWire device.
FireWire is compiled into GENERIC (device firewire/device sbp;
crom_next is statically linked at 0xffffffff804bdc90), so the bug
ships in every default kernel. The three callers in
sys/dev/disk/sbp/sbp.c:405,549,595 use struct crom_context cc as a
local stack variable β so the OOB write lands on the kernel stack,
corrupting the return address / saved frame.
Build
cc -O0 -g -o harness harness.c
Run
./harness
Expected (bug present β unpatched fwcrom.c)
Exit code 1. Output ends with:
>>> BUG CONFIRMED: crom_next wrote &stack[10] OUT OF BOUNDS.
The overflow slot (&cc.stack[10], immediately past the array) is
overwritten: .dir becomes a pointer into the crafted ROM, .index
becomes 0.
Expected (fixed β fwcrom.c with >= CROM_MAX_DEPTH - 1)
Exit code 0. Output contains:
crom_next: too deep >>> canary intact: no OOB write (guard fired correctly).
Fix
fix.diff: change line 115 from >= CROM_MAX_DEPTH to
>= CROM_MAX_DEPTH - 1. At depth 9 the test 9 >= 9 is true, the
guard fires, and the descent to depth 10 / stack[10] never happens.
DF-1083 β Verdict
REPRODUCED (code-level off-by-one confirmed via verbatim-source harness; live kernel trigger requires FireWire hardware absent from the QEMU guest).
Severity: High (kernel stack OOB write from an external device's data).
The bug
crom_next() in sys/bus/firewire/fwcrom.c:105 walks an IEEE-1212
Configuration ROM. When it encounters a Directory-type entry (CSRTYPE_D)
it descends one level:
if ((reg->key & CSRTYPE_MASK) == CSRTYPE_D) {
if (cc->depth >= CROM_MAX_DEPTH) { /* line 115 β BUG */
kprintf("crom_next: too deep\n");
goto again;
}
cc->depth ++; /* line 119 */
ptr = &cc->stack[cc->depth]; /* line 121 β OOB */
ptr->dir = (struct csrdirectory *)(reg + reg->val); /* line 122 */
ptr->index = 0; /* line 123 */
goto check;
}
CROM_MAX_DEPTH is 10 (iec13213.h:200) and stack is declared
struct crom_ptr stack[CROM_MAX_DEPTH] (iec13213.h:208) β valid indices
0..9. At cc->depth == 9 the guard 9 >= 10 evaluates false, so
depth is incremented to 10 and &cc->stack[10] is written. That
slot is past the end of the struct, i.e. past the end of the kernel
stack frame that contains cc.
The write is a full struct crom_ptr β 16 bytes (an 8-byte pointer
+ 4-byte int + 4 padding):
- ptr->dir = (struct csrdirectory *)(reg + reg->val) β a pointer the
attacker controls via the val field (24-bit offset into the ROM);
- ptr->index = 0 (fixed).
In-kernel reachability & impact ceiling
The vulnerable code is statically linked into the default GENERIC
kernel (sys/config/X86_64_GENERIC: device firewire, device sbp;
nm /boot/kernel/kernel shows crom_next at 0xffffffff804bdc90).
All three callers use struct crom_context cc as a local (stack)
variable:
- sbp_alloc_lun() β sys/dev/disk/sbp/sbp.c:405
- sbp_alloc_target()β sys/dev/disk/sbp/sbp.c:549
- sbp_alloc_dev() β sys/dev/disk/sbp/sbp.c:595 (via crom_has_specver,
which also has a stack-local struct crom_context)
These run during SBP-2 (SCSI-over-FireWire) target enumeration. The
cc struct (168 bytes) sits on the kernel stack; &cc.stack[10] at
offset 168 writes 16 bytes into the adjacent stack region β typically
saved registers / saved frame pointer / return address.
Realistic threat model: a malicious external FireWire device presents
a Configuration ROM nested 10 directories deep. When the host kernel
attaches it, crom_next overwrites the return address of the SBP attach
function on the kernel stack. On a kernel without SMEP/SMAP (or with an
appropriate gadget) this is kernel code execution β local privilege
escalation or remote (physical FireWire access) RCE.
Exploitation note: This guest has no FireWire controller, so the
in-kernel path cannot be triggered live. The primitive is proven at the
harness level (verbatim kernel code, crafted attacker ROM). This is the
same situation as DF-0594/0616/0281 (latent bug reachable only with
absent hardware). The OOB write is 16 bytes, partially attacker-
controlled (the dir pointer is set to an attacker-chosen offset into
the ROM; index is fixed at 0). Converting this to uid=0 requires a
FireWire controller β not available here β so no live escalation is
demonstrated; the bug is confirmed as a real stack-corruption primitive.
Proof (harness)
harness.c compiles the verbatim crom_init_context/crom_get/
crom_next (fwcrom.c:62-143) and the exact structures (iec13213.h),
then feeds a crafted 10-deep-nested Configuration ROM (25 words).
A struct crom_ptr overflow_slot is placed immediately after
cc.stack[9] β exactly where the buggy code writes &cc.stack[10] β
filled with sentinels and checked after the walk.
Before fix (unpatched logic):
overflow_slot (== &stack[10]) AFTER walk: .dir = 0x00007fffffdfd814 (sentinel was 0xDEADBEEFDEADBEEF) .index = 0x00000000 (sentinel was 0x12345678) >>> BUG CONFIRMED: crom_next wrote &stack[10] OUT OF BOUNDS.
Deterministic across 3 runs.
After fix (>= CROM_MAX_DEPTH - 1):
crom_next: too deep overflow_slot (== &stack[10]) AFTER walk: .dir = 0xdeadbeefdeadbeef (sentinel was 0xDEADBEEFDEADBEEF) .index = 0x12345678 (sentinel was 0x12345678) >>> canary intact: no OOB write (guard fired correctly).
Fix
fix.diff β one-line change at fwcrom.c:115:
- if (cc->depth >= CROM_MAX_DEPTH) {
+ if (cc->depth >= CROM_MAX_DEPTH - 1) {
At depth 9 the test 9 >= 9 is true β the "too deep" guard fires β no
descent, no write to stack[10]. The fix is minimal and targeted at the
root cause (off-by-one in the boundary check).
Fix validation (Phase 8)
- Baseline (#0, unpatched): harness shows BUG CONFIRMED (canary corrupted, exit 1).
- Fix applied to
/usr/src/sys/bus/firewire/fwcrom.c, kernel built (make -j6 nativekernel KERNCONF=X86_64_GENERIC, rc=0), installed, booted (6.5-DEVELOPMENT #1, healthy, ssh responsive). - Fixed harness logic: canary intact, "too deep" guard fires, exit 0.
- fix_status: fixed β the one-line change closes the OOB write and the patched kernel compiles and boots cleanly.
Because the live kernel path needs FireWire hardware (absent in QEMU),
fix_status is fixed on the basis of (a) the harness before/after and
(b) the patched kernel building + booting + containing the corrected
line β consistent with the not_testable-augmented standard for
hardware-gated latent bugs.
Fix verification
fixedVALIDATED: baseline stack[10] corrupted; patched 'too deep' guard, canary intact. Compile+boot+harness.
BEFORE: stack[10] .dir corrupted. AFTER: canary intact exit 0.
Confirmed kernel references
Detail
Exploit chain
none -- no FireWire HW. Primitive: 16B stack OOB write (.dir attacker ptr, .index=0). Stack-based, no slab grooming. RCE ceiling with real FW HW.
Evidence (decisive lines)
BEFORE: stack[10] .dir=0x7fffffdfd814 overwritten (sentinel deadbeef). AFTER: 'too deep' guard, canary intact.
PoC changes
Authored: harness.c (verbatim crom_next + structs), harness_fixed.c, fix.diff (>= to >= CROM_MAX_DEPTH-1), VERDICT.md, manifest.json.
Verified recommended fix
Change >= CROM_MAX_DEPTH to >= CROM_MAX_DEPTH-1 at fwcrom.c:115. Full diff in findings/poc/1083/fix.diff.
Verdict
REPRODUCED (harness). crom_next fwcrom.c:115 off-by-one: depth>=CROM_MAX_DEPTH(10) false at depth 9, depth++ to 10, &stack[10] OOB write (16B struct crom_ptr, valid 0..9). FireWire+sbp in GENERIC. Callers use stack-local cc -> kernel stack OOB. No FireWire HW on guest.
No comments yet.