β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
1083

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/1083 Β· 12 files
FileTypeDescriptionSize
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
README.md readme build/run/expected + mechanism summary
↓ download 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.

VERDICT.md verdict full narrative: mechanism, reachability, proof, fix validation
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: baseline stack[10] corrupted; patched 'too deep' guard, canary intact. Compile+boot+harness.

BEFORE: stack[10] .dir corrupted. AFTER: canary intact exit 0.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 16 04:03:37 UTC 2026

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.