IOAPIC topology (overlapping GSI bases, duplicate addresses, >16 entries) unvalidated at probe -> unconditional ioapic_add/ioapic_config boot panics
Summary
Probe checks only count>=1/zero-base/nonzero-address/Id!=255; enumerate feeds raw firmware fields to ioapic_add which panic()s on GSI overlap and duplicate address, and ioapic_config panics above 16 IOAPICs. Real injected MADT: 'panic: ioapic_add: overlapped gsi, base 0 npin 1, hit base 0, npin 24' after the full LAPIC/SMP stage - unconditional (no INVARIANTS needed). Probe-time topology validation fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5431 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| gen_madt.py | β | 4.8 KB | view raw | |
| build.sh | β | 124 B | view raw | |
| run.sh | β | 298 B | view raw | |
| run.log | β | 8.3 KB | view raw | |
| env.txt | β | 2.1 KB | view raw | |
| fix.diff | β | 1.5 KB | view raw |
DF-5431 β MADT IOAPIC topology never validated at probe β deterministic ioapic_add boot panic
What
madt_ioapic_probe() (acpi_madt.c:638-666) validates only: β₯1 IOAPIC entry,
some entry with GlobalIrqBase == 0, non-zero addresses, Id != 255.
It never checks that the described GSI ranges are disjoint, that addresses
are unique, or that there are β€16 IOAPICs. madt_ioapic_enum_callback
(acpi_madt.c:722-739) then feeds the raw entries to ioapic_add(), which
panic()s unconditionally on overlap (ioapic.c:240), duplicate address
(ioapic.c:245), and ioapic_config panics on >16 IOAPICs (ioapic.c:137-138).
Rig
Identical to ../DF-5430: hint.acpi.0.rsdp=0x40000000 + QEMU
-device loader blob with crafted RSDPβRSDTβMADT.
Build
python3 gen_madt.py ioapic_overlap /tmp/madt_ioapic_overlap.bin
Run
vm.sh run_root 'printf "hint.acpi.0.rsdp=0x40000000\nboot_verbose=YES\n" >> /boot/loader.conf; sync' vm.sh down QEMU_EXTRA="-device loader,file=/tmp/madt_ioapic_overlap.bin,addr=0x40000000,force-raw=on" vm.sh up vm.sh log
Expected output (run.log:87-89)
ACPI MADT: IOAPIC addr 0xfec00000, apic id 0, gsi base 0 ACPI MADT: IOAPIC addr 0xfec88000, apic id 1, gsi base 0 panic: ioapic_add: overlapped gsi, base 0 npin 1, hit base 0, npin 24
Trigger table: valid BSP+AP LAPIC entries plus two IO_APIC entries both declaring GSI base 0 (first at the real QEMU IOAPIC 0xFEC00000, second on unmapped MMIO whose VER read yields npin=1 β any overlap works).
Sibling findings: DF-1034 (mptable INT panic, Low), DF-5339 family (probe/enumerate asymmetry). Same firmware-fed boot-time-DoS class.
DF-5431 β VERDICT
1. Reproduced? YES β real kernel-boot panic
run.log:87-89 (kernel v6.5.0.1712.g89e6a #1 Mon Jun 29 clean-install build):
ACPI MADT: IOAPIC addr 0xfec00000, apic id 0, gsi base 0 ACPI MADT: IOAPIC addr 0xfec88000, apic id 1, gsi base 0 panic: ioapic_add: overlapped gsi, base 0 npin 1, hit base 0, npin 24
Boot completed the entire LAPIC/SMP stage (ACPI CPUS = 2, AP started) and
panicked in ioapic_config at SI_BOOT2_IOAPIC.
2. Why it is real (path:line)
- Probe side accepts the table:
madt_ioapic_probe_callback(acpi_madt.c:615-633) checks onlyAddress != 0(:619-622) andId != APICID_MAX(:623-628);madt_ioapic_probe(:652-662) adds onlyioapic_cnt != 0and "some entry has GlobalIrqBase 0". The crafted table satisfies all of these. - Enumerate side:
madt_ioapic_enum_callback(acpi_madt.c:728-738) callsioapic_map/ioapic_read/ioapic_addwith the raw firmware fields.ioapic_add()panics on GSI overlap (ioapic.c:233-243) and duplicate address (ioapic.c:244-245);ioapic_configpanics above 16 IOAPICs (ioapic.c:136-138). All three arepanic(), present in production kernels β this does NOT depend on INVARIANTS (unlike DF-5430).
Two more members of the same class demonstrated by inspection (not booted): two identical IO_APIC addresses β "duplicated addr" panic; 17 IO_APIC entries pass probe (count never capped) β "more than 16 I/O APIC" panic.
Related secondary robustness gap noted (not separately booted):
GlobalIrqBase is a raw UINT32 truncated to int gsi_base in
ioapic_add(void *, int, int) β bases β₯ 2^31 overflow gsi_end
(ioapic.c:231), which can defeat the overlap check arithmetic.
3. Threat model
Same as DF-5430: firmware/bootloader-controlled ACPI tables
(hint.acpi.0.rsdp vector demonstrated); no unprivileged in-guest path.
Deterministic boot-time panic = DoS, unconditional on kernel build.
Low severity, matching the accepted DF-1034/DF-5339 bar.
4. Exploit chain
None β controlled panic() before any memory unsafety.
5. Fix
fix.diff adds probe-side topology sanity within acpi_madt.c: duplicate
IOAPIC address, duplicate GSI base (two IOAPICs sharing a base always
overlap, since every IOAPIC has β₯1 pin), and a 16-entry cap β each returns
EINVAL so the enumerator is rejected gracefully and ioapic_config falls
back. Full npin-aware overlap detection needs MMIO reads at probe time and
belongs with ioapic.c's panicβrejection conversion (noted in the finding).
git apply --check passes. Not kernel-build-validated this run (single
mandatory build slot spent on DF-5432; this is a Low boot panic β DF-5339
precedent).
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
not_testablefix.diff (cbarg gains address/base tracking arrays; duplicates, shared GSI base and count cap return EINVAL) passes git apply --check. Not kernel-build-validated this run (mandatory slot spent on DF-5432; Low boot-panic class, DF-5339/DF-1034 precedent). Full npin-aware overlap needs MMIO reads at probe and belongs with converting ioapic.c panics to rejections.
findings/poc/DF-5431/fix.diff (git apply --check passes); VERDICT.md section 5
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log:87-89: two 'ACPI MADT: IOAPIC addr ... gsi base 0' entries then 'panic: ioapic_add: overlapped gsi, base 0 npin 1, hit base 0, npin 24'. Trigger table: valid LAPIC entries + IOAPIC(0xfec00000, base 0) + IOAPIC(0xfec88000, base 0).
PoC changes
No seed PoC; gen_madt.py scenario ioapic_overlap authored from scratch.
Verified recommended fix
probe: reject duplicate IOAPIC addresses, duplicate GSI bases and >16 IOAPIC entries (EINVAL) so ioapic_config falls back instead of panic()ing
Verdict
Deterministic unconditional boot panic from a crafted MADT IOAPIC topology: probe validates only count>=1, a GSI base of zero, non-zero addresses and Id!=255 (acpi_madt.c:615-662); enumerate feeds raw entries to ioapic_add() which panic()s on GSI overlap (ioapic.c:240), duplicate address (ioapic.c:245), and ioapic_config panics >16 IOAPICs (ioapic.c:137). Reproduced with two IO_APIC entries sharing GSI base 0: 'panic: ioapic_add: overlapped gsi, base 0 npin 1, hit base 0, npin 24' after the full LAPIC/SMP stage completed. panic() is unconditional (no INVARIANTS needed, unlike DF-5430). MADT sibling of DF-1034/DF-5339. Firmware-fed.
No comments yet.