⬢ DragonFlyBSD Kernel Audit
DF-5352 / verdict.json
← back to finding ↓ download raw
{
  "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"
}