DF-5452 / verdict.json
{ "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)" } |