DF-5352 / verdict.json
{ "finding_id": "DF-5352", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "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.", "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 cpus 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": "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)", "kernel_refs": [ "sys/platform/pc64/apic/ioapic_abi.c:987", "sys/platform/pc64/apic/ioapic_abi.c:999", "sys/platform/pc64/apic/ioapic_abi.c:1005", "sys/platform/pc64/apic/ioapic_abi.c:1038", "sys/platform/pc64/apic/ioapic_abi.c:763", "sys/platform/pc64/apic/ioapic.c:415", "sys/sys/systm.h:94", "sys/sys/machintr.h:110" ], "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", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sat Oct 3 08:13:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_NOINV x86_64 (production-path run; INVARIANTS run on stock X86_64_GENERIC #0 Jul 2 06:02:54; fix run on #1 08:38:15)", "runtime_sec": 3600, "guest_dirty": 1, "build_cmd": "sh build.sh (in-tree kmod build of legacy_crash.ko via /usr/src/sys/conf/kmod.mk)", "run_cmd": "kldload /usr/obj/usr/src/sys/modules/msiabi/legacy_crash.ko", "code_hash": "5f1ffa901eaeda8998dc4b82ec3a27f38b9421750b1808b78d0e22c075ab5b6e (legacy_crash.c)", "notes": "Also in the same class (cited in the finding, not separately executed): ioapic_abi_legacy_intr_cpuid() ioapic_abi.c:1121 silently returns cpu0 for non-legacy irqs (comment at :1135 admits driver misuse); ioapic_abi_msi_map() ioapic_abi.c:1413 hands im_msi_base=-1 to msi_map for non-MSI intrs (values only, no memory unsafety). Guest returned via vm.sh reset with-src.", "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)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.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.", "fix_evidence": "findings/poc/DF-5352/run_fixed.log; fix.diff" } |