nexus_activate_resource() is not idempotent: re-activation of an active MMIO resource double-maps and orphans kernel_map KVA + RW PTEs; no mapping rollback on activation failure
Summary
pmap_mapdev runs BEFORE the idempotent rman_activate_resource and overwrites r_virtual - every re-activation of an already-RF_ACTIVE resource returns success but allocates a fresh kernel_map range + RW PTEs for the same physical window, permanently orphaning the previous mapping (and a failed rman activation strands the fresh one). Guest-verified: 16 alloc/activate/release cycles stranded exactly 16 pages (kernel_map->size +65536, first mapping still dereferenceable after full release); fix shows vaddr1==vaddr2, 0 leaked. Driver-bug amplifier (reachable re-activation wrappers in pci/acpi/exca/pccbb). Guard + rollback fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5450 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| nxmap.c | β | 4.2 KB | view raw | |
| build.sh | β | 81 B | view raw | |
| run.sh | β | 155 B | view raw | |
| build.log | β | 1.1 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| run.2.log | β | 758 B | view raw | |
| fix_run.log | β | 928 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| fix.combined.diff | β | 1.7 KB | view raw | |
| env.txt | β | 687 B | view raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-5450 VERDICT β REPRODUCED (KVA + stale-RW-PTE leak)
Finding
nexus_activate_resource() β sys/platform/pc64/x86_64/nexus.c:420-451 β
performs its pmap_mapdev() mapping step unconditionally, before calling
the idempotent rman_activate_resource() (nexus.c:450), then overwrites the
resource's only mapping handle with rman_set_virtual(r, vaddr)
(nexus.c:446). Three consequences:
- Re-activation double-maps.
bus_activate_resource()on an alreadyRF_ACTIVEMMIO resource returns success (rman idempotence, sys/kern/subr_rman.c:419-424) butpmap_mapdev_attr()(sys/platform/pc64/x86_64/pmap.c:6192-6219) allocates a freshkernel_maprange + RW PTEs on every call. The previous mapping is orphaned: nothing can ever unmap it, becauser_virtualwas overwritten.nexus_setup_intr()documents the very contract being violated: "We depend here on rman_activate_resource() being idempotent" (nexus.c:513-515). - No rollback on activation failure. If
rman_activate_resource()fails (e.g. RF_TIMESHARE share conflict, subr_rman.c:426-438) after the mapping was created,nexus_activate_resource()returns the error leaving the mapping in place; every caller releases the resource without unmapping (nexus.c:410-414 checks only the return code;nexus_release_resource()at nexus.c:475-479 skips deactivate entirely for a resource whose RF_ACTIVE was never set). - Reachable re-activation wrappers (no RF_ACTIVE pre-check anywhere in
the chain):
- sys/bus/pci/pci.c:4073-4085 β re-allocation of an already allocated rid
with RF_ACTIVE re-activates unconditionally;
- sys/dev/acpica/acpi.c:1163-1169;
- sys/dev/pccard/exca/exca.c:862-868; pccbb.c:1188-1194,1339-1348;
- any driver calling the public
bus_activate_resource()API on an active resource.
Reproduction (baseline, kernel #0 Jul 2 2026, INVARIANTS)
KLD nxmap.ko (nexus child driver) β see run.log / run.2.log:
- bus_alloc_resource_any(..., RF_ACTIVE) then a second
bus_activate_resource() on the same resource: err=0, and
vaddr2 != vaddr1, both mapping the SAME physical page (same_phys=YES).
- 16 x alloc/activate/release cycles: kernel_map->size grows by exactly
16 pages (65536 bytes) and never returns; the final vaddr2 sits exactly
0x10000 above the first vaddr1.
- After bus_release_resource() of every resource, the first mapping
(0xffffffff82601000) is STILL dereferenceable β a stale RW PTE mapping
physical MMIO 0xE0000000 persists in kernel_pmap forever.
Impact
Permanent kernel_map KVA exhaustion vector (one roundup(size,PAGE) range
per re-activation; MMIO BAR-sized on the pci.c re-alloc path) plus permanent
stale RW kernel PTEs mapping device memory after the owning driver has torn
down β kernel-pmap hygiene degradation compounding with later kmem_alloc
aliasing. Same impact family as DF-1074 (deactivate-side truncation), but
the activate-side root cause and trigger (any re-activation, no >4GiB BAR
required). No data disclosure to userspace; not a privilege escalation.
Preconditions: a driver (in-tree wrapper paths exist, none currently
double-activate in-tree) or driver-author bug re-activating MMIO. Severity
Low.
Fix validation (kernel #1 Oct 5 17:59:42, fix.combined.diff)
fix.diff adds: (a) early return when rman_get_flags(r) & RF_ACTIVE
(idempotence for the mapping step), (b) rollback pmap_unmapdev() when
rman_activate_resource() fails after a fresh mapping.
Patched result (fix_run.log): vaddr1 == vaddr2 on every cycle, kernel_map
size unchanged (leaked pages: 0), no orphan mapping after release. Full
normal boot and device attach through nexus unchanged.
Kernel refs
sys/platform/pc64/x86_64/nexus.c:427-450, 446, 450, 513-515, 410-414, 475-479 sys/platform/pc64/x86_64/pmap.c:6192-6219 sys/kern/subr_rman.c:419-424, 426-438 sys/bus/pci/pci.c:4073-4085 sys/dev/acpica/acpi.c:1163-1169 sys/dev/pccard/exca/exca.c:862-868
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
fixedfix.combined.diff applied in guest /usr/src, nativekernel rebuilt and booted (kernel #1): second activation is now a no-op for the mapping (vaddr1==vaddr2), kernel_map->size unchanged over 16 cycles (leaked pages: 0), no orphan mapping after release
findings/poc/DF-5450/fix_run.log (patched-kernel dmesg: leaked pages: 0, stale: N/A (v1 == v2, single mapping))
Confirmed kernel references
Detail
Exploit chain
No userland chain: trigger is driver-side re-activation (pci.c:4073-4085 re-activates an allocated rid with RF_ACTIVE; acpi/exca/pccbb same pattern; any driver calling bus_activate_resource() on an active resource). Each re-activation permanently leaks roundup(size,PAGE) of kernel_map KVA and a stale RW MMIO PTE set -> long-run KVA exhaustion / kernel-pmap hygiene degradation. Not a data disclosure or privesc.
Evidence (decisive lines)
run.log lines 'err2ndact=0 vaddr1=...2601000 vaddr2=...2611000 same_phys=YES' + 'leaked pages: 16' + 'stale RW PTE still present: YES'; run.2.log single-cycle +1 page; fix_run.log (patched): vaddr1==vaddr2, leaked pages: 0
PoC changes
Added 16-cycle accumulation loop, kernel_map->size accounting, guarded orphan-read (safe on patched kernels); final module builds clean with -Werror
Verified recommended fix
Guard nexus_activate_resource(): if (rman_get_flags(r) & RF_ACTIVE) return rman_activate_resource(r); and pmap_unmapdev() the fresh mapping when rman_activate_resource() fails
Verdict
REPRODUCED on the live guest: nexus_activate_resource() (nexus.c:420-451) maps MMIO with pmap_mapdev() before the idempotent rman_activate_resource() and overwrites r_virtual, so re-activating an active resource returns success but allocates a second kernel_map range + RW PTEs for the same physical window, orphaning the first permanently. 16 alloc/activate/release cycles stranded exactly 16 pages (kernel_map->size +65536, never returned) and left the first mapping dereferenceable after full release (stale RW PTE). Fix (idempotence guard + failure rollback) validated on a rebuilt kernel: single mapping, 0 leaked pages, no orphan PTE.
No comments yet.