ioapic_abi_legacy_intr_config(): driver-facing ABI panics production kernels when called with a non-legacy IRQ (KKASSERT-only validation, void return)
Summary
bus_config_intr on a non-legacy IRQ (e.g. the driver's own just-allocated MSI-X vector): KKASSERT-only validation compiles out on production kernels, the LEGACY search falls through with cpuid==ncpus, flags written into a wrong-type map, then ioapic_gsi_pin(-1) panics 'no I/O APIC'; INVARIANTS kernels panic at the assert. Reproduced BOTH kernel flavors on the guest via KLD (msix_alloc until irq>=24 then config) + fix validated (silent return, valid config unaffected). Runtime-guard-and-return fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5352 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| legacy_crash.c | β | 2.9 KB | view raw | |
| build.sh | β | 119 B | view raw | |
| run.sh | β | 108 B | view raw | |
| run_prodkernel.log | β | 609 B | view raw | |
| panic_kkassert.txt | β | 1.3 KB | view raw | |
| run_invkernel.log | β | 0 B | β download | |
| run_fixed.log | β | 582 B | view raw | |
| env.txt | β | 988 B | view raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| fix.diff | β | 891 B | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-5352 VERDICT β reproduced (kernel panic from driver-facing legacy-config ABI), Low severity
Bottom line
REPRODUCED on the guest both ways:
- production-behaviour kernel (X86_64_GENERIC minus
options INVARIANTS; stock GENERIC ships without INVARIANTS): a KLD callingMachIntrABI.legacy_intr_config(irq=24, LEVEL, LOW)where irq 24 is an MSI-X map (im_gsi == -1) panics the kernel:panic: ioapic_gsi_search: no I/O APIC(ioapic.c:415-427, reached via ioapic_abi.c:1038ioapic_gsi_pin(map->im_gsi)). - INVARIANTS kernel: the same trigger panics at the debug assert
panic: assertion "irq >= 0 && irq < ioapic_abi_legacy_irq_max" failed in ioapic_abi_legacy_intr_config at ioapic_abi.c:999.
Either way a single bad driver call β bus_config_intr() on an IRQ that
isn't a legacy map, e.g. the MSI/MSI-X vector the driver itself just
allocated β is promoted to a kernel panic. The function returns void; no
error path exists. On production kernels the guard KKASSERTs at
ioapic_abi.c:999/:1005 compile to nothing (sys/sys/systm.h:94-122), the
search loop at :1000-1004 falls through with cpuid==ncpus, and :1022/:1035
write flag/trigger fields into a wrong-type map before :1038 feeds
im_gsi=-1 into the gsi lookup. Fix validated: with runtime range/type
checks (early return) the identical kldload returns cleanly, no panic.
Reproduction (baseline)
Production kernel (run_prodkernel.log):
legacy_crash: msix_alloc rc=0 irq=24
legacy_crash: calling legacy_intr_config(irq=24, LEVEL, LOW); production kernel should panic in ioapic_gsi_search (im_gsi == -1)
panic: ioapic_gsi_search: no I/O APIC
ioapic_gsi_search() at ioapic_gsi_search+0x3a 0xffffffff80b7350a
Debugger("panic")
INVARIANTS kernel (panic_kkassert.txt β captured 07:58 UTC on the stock X86_64_GENERIC):
legacy_crash: ... irq=24 panic: assertion "irq >= 0 && irq < ioapic_abi_legacy_irq_max" failed in ioapic_abi_legacy_intr_config at /usr/src/sys/platform/pc64/apic/ioapic_abi.c:999
Note on trigger selection: the first attempt used irq 19 and returned without panic β on this QEMU guest irqs 16..23 are legacy level/low maps (on some CPU), so ioapic_abi_legacy_intr_config() found a LEGACY map, matched trig/pola, and took the early return at ioapic_abi.c:1024-1025. The module therefore loops msix_alloc until it lands an irq >= 24. This also documents the correct-behavior baseline: valid legacy requests are unaffected.
Impact
DoS/panic from privileged (driver) misuse; no memory corruption on the demonstrated path (the -1 gsi reaches ioapic_gsi_search which panics by design instead of indexing OOB). No unprivileged entry point. Severity Low. Same pattern class as DF-5351: INVARIANTS-only guards on a driver-facing ABI (also cited: ioapic_abi_legacy_intr_cpuid ioapic_abi.c:1121 silently returning cpu0, ioapic_abi_msi_map ioapic_abi.c:1413 passing im_msi_base=-1 to msi_map for non-MSI intrs β benign values, no memory unsafety).
Fix validation (fix.diff)
Applied to guest /usr/src, incremental X86_64_NOINV rebuild (#1 08:38:15), reboot, identical PoC (run_fixed.log):
legacy_crash: msix_alloc rc=0 irq=24 legacy_crash: calling legacy_intr_config(irq=24, LEVEL, LOW); ... legacy_crash: RETURNED WITHOUT PANIC (unexpected) LC_RC=0, guest up
No panic; correct legacy configurations (irq 19 path) still take the normal route; MSI-X allocations/releases unaffected.
poc_changes
- DECLARE_MODULE moduledata_t form (see DF-5351 VERDICT).
- Loop msix allocations until irq >= 24 (irqs 16..23 are legacy on this guest) β the naive "first msix irq" trigger hits a real legacy map and returns cleanly.
- Built with the in-tree module recipe (see build.sh).
Kernel references
- sys/platform/pc64/apic/ioapic_abi.c:987-1053 (ioapic_abi_legacy_intr_config)
- sys/platform/pc64/apic/ioapic_abi.c:999, :1005 (KKASSERT-only guards)
- sys/platform/pc64/apic/ioapic_abi.c:1022, :1035-1036, :1038 (wrong-map writes + gsi_pin(-1))
- sys/platform/pc64/apic/ioapic_abi.c:763-764 (im_gsi/im_msi_base init to -1)
- sys/platform/pc64/apic/ioapic.c:409-427 (ioapic_gsi_pin β ioapic_gsi_search β panic)
- sys/sys/systm.h:94-122 (asserts compiled out without INVARIANTS)
- sys/sys/machintr.h:110 (machintr_legacy_intr_config macro β driver-facing)
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, X86_64_NOINV rebuilt (#1 08:38:15) and rebooted; identical PoC re-run: legacy_intr_config(irq=24, LEVEL, LOW) returns silently, 'RETURNED WITHOUT PANIC', LC_RC=0, guest stays up (run_fixed.log). No panic; valid legacy path (irq 19) still configured normally.
findings/poc/DF-5352/run_fixed.log; fix.diff
Confirmed kernel references
Detail
Exploit chain
Driver (KLD / any driver calling bus_config_intr() or MachIntrABI.legacy_intr_config()) passes a non-legacy IRQ, e.g. its own MSI-X vector -> KKASSERT guards are no-ops on production kernels -> cpuid exits == ncpus, map = last cpu's non-legacy map -> im_flags/im_trig/im_pola written into wrong-type map -> ioapic_gsi_pin(-1) -> ioapic_gsi_search(-1) -> panic('no I/O APIC'). DoS from privileged misuse; no memory corruption on this path (gsi_search panics by design); no unprivileged entry point.
Evidence (decisive lines)
run_prodkernel.log (production kernel: 'legacy_intr_config(irq=24, LEVEL, LOW)' then 'panic: ioapic_gsi_search: no I/O APIC' + frame ioapic_gsi_search+0x3a); panic_kkassert.txt (INVARIANTS kernel: assert at ioapic_abi.c:999); run_fixed.log (fixed kernel: 'RETURNED WITHOUT PANIC', LC_RC=0, guest up)
PoC changes
DECLARE_MODULE moduledata_t form; loop msix_alloc until irq >= 24 (on this guest irqs 16..23 are legacy level/low maps on some CPU, so the naive first-msix-irq trigger hits the normal early-return path at ioapic_abi.c:1024 and returns cleanly - kept as the correct-behavior baseline); in-tree kmod build recipe
Verified recommended fix
In ioapic_abi_legacy_intr_config(), replace the KKASSERTs with runtime checks: return early when irq is out of [0, ioapic_abi_legacy_irq_max) and when no CPU holds a LEGACY map for that irq (cpuid == ncpus)
Verdict
Reproduced: on a production-behaviour kernel (no INVARIANTS), calling the driver-facing legacy-config ABI with an IRQ that is an MSI-X map (im_gsi=-1) panics the kernel - panic: ioapic_gsi_search: no I/O APIC - because ioapic_abi_legacy_intr_config()'s only validation is KKASSERTs that compile out (ioapic_abi.c:999/:1005); the LEGACY search loop falls through, the wrong-type map is mutated, and ioapic_gsi_pin(-1) panics (ioapic.c:415-427). On the INVARIANTS kernel the same trigger panics at the :999 assert instead. Fix (runtime range/type checks with early return) makes the identical kldload return cleanly with no panic and leaves valid legacy configurations working.
No comments yet.