icu_abi_legacy_intr_config(): driver-facing ABI with KKASSERT-only irq bounds β silent OOB read-modify-write into icu_irqmaps on production kernels plus wild-index kernel page fault; returns void, no error path
Summary
The ICU twin of DF-5352: irq validated only by a compiled-out KKASSERT then &icu_irqmaps[0][irq] at a 16-byte stride - irq=1000 silently writes +16000B into cpu5's row and returns (void); irq=-100000000 dies at the exact predicted im_flags RMW (fault VA = base - 0x5F5E1000 + 0xc). Same class folded: enable/disable/setup/teardown KASSERT-only bounds and the msi_map type KASSERTs (silently wrong hardware values). Runtime bounds + type checks fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5465 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| icu_legacy_probe.c | β | 3.1 KB | view raw | |
| build.sh | β | 129 B | view raw | |
| run.sh | β | 179 B | view raw | |
| build.log | β | 11.2 KB | view raw | |
| run.log | β | 396 B | view raw | |
| run_fixed.log | β | 461 B | view raw | |
| panic_wild_irq.txt | β | 0 B | β download | |
| env.txt | β | 2.3 KB | view raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| fix.diff | β | 2.2 KB | view raw | |
| manifest.json | β | 977 B | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5465 VERDICT β reproduced (silent OOB RMW write + wild-index panic), driver-facing legacy-config ABI, Low severity
Bottom line
REPRODUCED on the production-behaviour kernel (X86_64_NOINV; stock
GENERIC equally affected): icu_abi_legacy_intr_config()
(icu_abi.c:366-397) validates its driver-supplied irq only with
KKASSERTs compiled out of production kernels. A driver calling
bus_config_intr() / MachIntrABI_ICU.legacy_intr_config():
- irq=1000: silently performs the OOB read-modify-WRITE
map->im_flags |= ICU_IMF_CONF(icu_abi.c:379) andmap->im_trig = trig(:389) at&icu_irqmaps[0][1000](+16000 bytes β another CPU's map row), then callselcr_write_trigger(1000, ...)(elcr.c:70-92) whose KASSERT is likewise gone (UB1 << irqshift + outb to the ELCR port) β returns void, no error, guest apparently healthy. - irq=-100000000:
Fatal trap 12: page fault while in kernel mode,fault virtual address = 0xffffffff2238a42c(= icu_irqmaps base 0xffffffff81a00420 β 0x5F5E1000 + 0xc β exactly the im_flags field of element β100000000),fault code = supervisor write data, **Stopped at icu_abi_legacy_intr_config+0x25: orl $0x1,0xc(%rbx)** β the|=RMW at icu_abi.c:379 faulting on a wild negative index.
On the INVARIANTS kernel the same call panics at the KKASSERT (icu_abi.c:373) β either way a driver bug is promoted to a kernel panic; on production kernels it is first a silent 16-byte-strided OOB write primitive at a driver-chosen offset.
How it was reproduced (baseline X86_64_NOINV, run.log / panic_wild_irq.txt)
icu_legacy_probe: step1 legacy_intr_config(1000, EDGE, HIGH): ... icu_legacy_probe: step1 RETURNED WITHOUT ERROR (void) -- OOB write done icu_legacy_probe: step2 legacy_intr_config(-100000000, ...): ... Fatal trap 12: page fault while in kernel mode fault virtual address = 0xffffffff2238a42c fault code = supervisor write data, page not present Stopped at icu_abi_legacy_intr_config+0x25: orl $0x1,0xc(%rbx) db>
The faulting instruction is byte-for-byte the predicted RMW
(im_flags at struct offset 0xc of a 16-byte element).
Primitive characterization
16-byte-strided OOB write at an attacker/driver-chosen index with
partially-fixed contents (|1 RMW on im_flags; small enum into
im_trig), plus the ELCR shadow/port write with a wild irq. Moderate
indices corrupt other CPUs' map rows silently; wild indices panic. Same
production-no-op-guard class as DF-5352 (ioapic twin); same-class
bullets folded into this finding: icu_abi_intr_enable/disable/setup/
teardown KASSERT-only bounds (icu_abi.c:178,:196,:263,:287) and
icu_abi_msi_map type/base KASSERTs only (icu_abi.c:628-634, silently
wrong msi_map(-1,...) hardware values). Trigger requires a
driver-facing call β already-privileged β hence Low severity.
Fix validation (fix.diff, applied to guest /usr/src, incremental rebuild)
Identical PoC on the fixed kernel (run_fixed.log):
step1 RETURNED WITHOUT ERROR (now rejected at the runtime bounds/
type check BEFORE the write)
step2 RETURNED WITHOUT PANIC (was: fatal trap)
guest healthy afterwards
Bad behavior gone; legal legacy config (irq<16 LEGACY maps) unaffected (bounds check uses ICU_HWI_VECTORS=16, type check passes for LEGACY).
Kernel references
- sys/platform/pc64/icu/icu_abi.c:373-376 (KKASSERT-only bounds + type)
- sys/platform/pc64/icu/icu_abi.c:379 (im_flags OOB RMW write β the faulting insn)
- sys/platform/pc64/icu/icu_abi.c:389,396 (im_trig write, elcr_write_trigger)
- sys/platform/pc64/icu/elcr.c:70-92 (KASSERT-only irq guard, 1<<irq UB)
- sys/sys/systm.h:94-122 (asserts compiled out without INVARIANTS)
- sys/sys/machintr.h:103 (bus_config_intr β legacy_intr_config shim)
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
fixedfix.diff applied to guest /usr/src/sys/platform/pc64/icu/icu_abi.c, X86_64_NOINV rebuilt and rebooted; identical PoC re-run: step1 returns at the bounds/type check before any write (no OOB RMW), step2 returns without panic (was: Fatal trap 12), guest healthy afterwards in the same boot (run_fixed.log). Legal legacy configuration of irq 0..15 LEGACY maps is unaffected.
run_fixed.log ('step1 RETURNED WITHOUT ERROR' with no write performed; 'step2 RETURNED WITHOUT PANIC'; uname/uptime OK)
Confirmed kernel references
Detail
Exploit chain
driver calls bus_config_intr(irq, trig, pola) / machintr_legacy_intr_config (sys/sys/machintr.h:103) with an out-of-range irq (probe bug, device hint, or an MSI vector) -> icu_abi.c:374 &icu_irqmaps[0][irq] with unbounded 16-byte stride -> :379 |= RMW and :389 enum store at driver-chosen offset (silent corruption of adjacent per-CPU map rows for moderate values; kernel-mode page fault for wild values) -> :396 elcr_write_trigger(irq) with the same wild index. Requires already-privileged driver-facing reach, hence Low.
Evidence (decisive lines)
run.log + panic_wild_irq.txt (step1 'RETURNED WITHOUT ERROR', step2 'Fatal trap 12 ... Stopped at icu_abi_legacy_intr_config+0x25: orl $0x1,0xc(%rbx)', fault VA arithmetic = icu_irqmaps 0xffffffff81a00420 - 0x5F5E1000 + 0xc); run_fixed.log (both steps return, no panic)
PoC changes
Rig written fresh (family pattern from DF-5352's legacy_crash.c, adapted from the ioapic struct to MachIntrABI_ICU and to the ICU code path: two-step single module β moderate index proves the silent OOB write survives, wild negative index proves the panic; no ioapic gsi_search involved on the ICU path so the fault itself is the crasher).
Verified recommended fix
Add runtime bounds (irq < 0 || irq >= ICU_HWI_VECTORS) and LEGACY type check returning early in icu_abi_legacy_intr_config(); same runtime bounds in intr_enable/disable/setup/teardown (see fix.diff).
Verdict
REPRODUCED on a production-behaviour kernel (X86_64_NOINV): icu_abi_legacy_intr_config() (icu_abi.c:366-397) validates its driver-supplied irq only with a KKASSERT (icu_abi.c:373) that compiles to nothing without INVARIANTS, and returns void with no error path. With irq=1000 it silently performs the OOB read-modify-write 'map->im_flags |= ICU_IMF_CONF' (icu_abi.c:379) and im_trig write (:389) at &icu_irqmaps[0][1000] (+16000 bytes, cpu5's map row) and feeds the wild irq to elcr_write_trigger (elcr.c:70-92, KASSERT-only guard, 1<
No comments yet.