⬢ DragonFlyBSD Kernel Audit
DF-5452 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5452",
  "status": "reproduced",
  "verdict": "REPRODUCED on the live guest: the plain documented driver API bus_alloc_resource(dev, SYS_RES_IRQ, rid, 5, 5, 1, flags) reaches nexus_alloc_resource with cpuid=-1 (subr_bus.c:3299 default) and the only guard on rm = &irq_rman[cpuid] is an INVARIANTS-gated KASSERT (nexus.c:377-378). On the guests INVARIANTS kernel it panicked exactly at nexus_alloc_resource (panic: nexus invalid cpuid: -1) while the same vector allocated fine through bus_alloc_legacy_irq_resource with a valid cpuid. On kernels built without options INVARIANTS the same call computes &irq_rman[-1] (sizeof(struct rman)=80 bytes below the array, measured) and feeds that OOB static storage to rman_reserve_resource()s lwkt token + TAILQ operations. Fix (real bounds check returning NULL) validated on a rebuilt kernel: diagnostic + graceful NULL, no panic, normal boot unchanged.",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "exploit_chain": "Driver-API-boundary defect: a nexus child (or grandchild behind a passthrough-only bus) using the obvious explicit-range IRQ allocation gets a panic on INVARIANTS kernels and uncontrolled OOB kernel-static access (wild token/TAILQ writes when adjacent statics are non-zero) on non-INVARIANTS production kernels. All in-tree callers currently route cpuid through validated rle->cpuid, so the trigger is a buggy or out-of-tree/KLD driver; no unprivileged-userland path found.",
  "evidence": "panic.txt: panic: nexus invalid cpuid: -1 backtrace nexus_alloc_resource <- bus_alloc_resource <- nxirq_attach, immediately after leg-1 success res=0xfffff800895d0868; fix_run.log: diagnostic + res=0, guest ALIVE on patched kernel",
  "kernel_refs": [
    "sys/platform/pc64/x86_64/nexus.c:376-379",
    "sys/sys/systm.h:94-112",
    "sys/kern/subr_bus.c:3293-3300",
    "sys/kern/subr_bus.c:3302-3309",
    "sys/kern/subr_rman.c:221-231"
  ],
  "poc_changes": "first attempt used vector 60 and tripped ioapic_abi_legacy_intr_cpuids own range assert (lesson: legacy helpers cap at 15); switched leg 1 to legacy IRQ 5 which allocates cleanly, keeping attribution on nexus",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 300,
  "guest_dirty": 1,
  "build_cmd": "cd /root/poc/nxirq && make   (KMOD=nxirq SRCS=nxirq.c device_if.h bus_if.h, bsd.kmod.mk)",
  "run_cmd": "kldload /root/poc/nxirq/nxirq.ko   (panics baseline kernel; capture via serial console vm.sh log)",
  "code_hash": "6a2f5b06159ad68eee227668fbd90fbbe8ad13df5abd8ed81eeb4f00c0228dd7",
  "notes": "Guest panicked during baseline verification (guest_dirty=1) and was reset afterward. X86_64_GENERIC ships WITH options INVARIANTS; custom kernels without it get the silent OOB variant. register_int() downstream KKASSERTs cpuid similarly (kern/kern_intr.c:262) \u2014 same INVARIANTS-only gating family.",
  "recommended_fix": "Replace the KASSERT at nexus.c:377 with a real bounds check (cpuid < 0 || cpuid >= ncpus -> kprintf + return NULL)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.combined.diff applied in guest /usr/src, nativekernel rebuilt and booted: leg 2 now returns res=0 with nexus_alloc_resource: invalid cpuid -1 diagnostic, no panic, module attaches, full boot with all nexus IRQ allocations unaffected",
  "fix_evidence": "findings/poc/DF-5452/fix_run.log (patched-kernel dmesg + ALIVE uptime)"
}