β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5465

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5465 Β· 12 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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) and map->im_trig = trig (:389) at &icu_irqmaps[0][1000] (+16000 bytes β€” another CPU's map row), then calls elcr_write_trigger(1000, ...) (elcr.c:70-92) whose KASSERT is likewise gone (UB 1 << irq shift + 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

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.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)
↓ fix.diffper-fix-DF-5465

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< early return) validated on a rebuilt kernel: both steps return without write and without panic, guest healthy.