# 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-bit `crc_len` bitfield **before** the `CROM_END` bound check on the right
  operand is even evaluated (C `&&` evaluates left first);
* `sys/bus/firewire/fwcrom.c:134` — second unguarded `ptr->dir->crc_len` load.

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

1. **Userspace model** (`c5497_user.c`, production `fwcrom.c` compiled for
   userland as `fwcontrol(8)` links it, ROM placed against a `PROT_NONE` guard
   page):
   * V=300 → SIGSEGV at `rom+1226` (predicted load window `rom+1224..1227`;
     GCC emits a halfword load for the `crc_len` bitfield): the load is
     200 bytes past the 1024-byte ROM **and happens before the `CROM_END`
     check that would have rejected the directory** (`run.log`).
   * V=0xffffff → SIGSEGV at `rom+0x4000014` — the full +64 MB reach.
   * V=100 (in-ROM) → clean walk (control).
2. **KLD oracle mode** (production `fwcrom.c` in `df5497.ko`; slab groom:
   64 same-class 1024-byte blocks patterned with `crc_len=1`, every other one
   freed, model ROM allocated into a hole): four
   `crom_next: bound check failed` lines — the production parser read
   `crc_len` **from 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).
3. **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`, **RIP `dfh_crom_next+0x53`** (the `crc_len`
   load), 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 (16*0x100000 & 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.
