nexus_deactivate_resource truncates MMIO size to 32 bits, leaving stale PTEs and orphaning KVA for >4 GiB BARs
| Field | Value |
|---|---|
| ID | DF-1074 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-197 Numeric Truncation Error |
| File | sys/platform/pc64/x86_64/nexus.c |
| Lines | 462, 464, 465 (nexus_deactivate_resource), 437/444 (activate counterpart) |
| Area | platform/pc64/x86_64 (nexus root bus / rman MMIO unmap) |
| Confidence | likely |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
nexus_deactivate_resource declares its local size variable as u_int32_t while the
symmetric nexus_activate_resource correctly uses u_int64_t. rman_get_size() returns
u_long (64-bit on pc64) and pmap_unmapdev() takes vm_size_t (64-bit per
cpu/x86_64/include/types.h:40), so for any MMIO resource larger than 4 GiB the high 32
bits of the size are silently dropped on the unmap path, leaving stale RW PTEs in
kernel_pmap and orphaning the upper portion of the KVA range that pmap_mapdev had
originally allocated.
Root cause
nexus_activate_resource (sys/platform/pc64/x86_64/nexus.c:437) correctly declares
u_int64_t psize; and calls pmap_mapdev(paddr - poffs, psize + poffs) (nexus.c:444) β so
a >4 GiB MMIO region is fully mapped with a 64-bit size.
nexus_deactivate_resource (sys/platform/pc64/x86_64/nexus.c:462) declares
u_int32_t psize; instead, then assigns psize = rman_get_size(r); (nexus.c:464).
rman_get_size is ((r)->r_end - (r)->r_start + 1) with both fields u_long
(sys/sys/rman.h:103, 104, 146), so the assignment narrows to 32 bits β and passes that to
pmap_unmapdev((vm_offset_t)rman_get_virtual(r), psize) (nexus.c:465).
pmap_unmapdev (sys/platform/pc64/x86_64/pmap.c:6223-6232) then computes
size = roundup(offset + size, PAGE_SIZE); pmap_qremove(va, size >> PAGE_SHIFT);
kmem_free(kernel_map, base, size); using the truncated size.
mem_rman manages [0, ULONG_MAX] (nexus.c:225 β rman_manage_region(&mem_rman, 0, ~0)
where ~0 widens to ULONG_MAX), so a >4 GiB SYS_RES_MEMORY allocation is legal and
reachable from the PCI bus driver for any device whose BAR is >= 4 GiB (e.g. modern GPUs
with Resizable BAR / Above-4G-Decoding, large NVMe controllers).
Threat model & preconditions
- Attacker position: Whoever can trigger the alloc / deactivate cycle (typically requires local privileged access to manipulate driver attach / detach, or physical hot-plug).
- Privileges gained or impact:
- KVA leak in
kernel_mapproportional to the un-unmapped upper fragment, compounding across cycles and eventually capable of exhausting kernel virtual address space (DoS / panic onkmem_allocfailure). - Stale PTEs in
kernel_pmapthat continue to map physical MMIO at RW privilege after the owning driver has torn down its state β a kernel-pmap hygiene defect that could compound with a laterkmem_allocaliasing the same range.
No direct privilege escalation or info leak to userspace was demonstrated; the impact is
availability plus kernel-pmap hygiene.
- Required config or capabilities: Hardware with a >4 GiB MMIO BAR (e.g. a discrete GPU
with Resizable BAR enabled in firmware) and the ability to force that driver to allocate
then release the BAR. Default kernel; no special config option beyond Resizable BAR
enabling.
- Reachability: Any kernel driver that allocates a SYS_RES_MEMORY resource of more
than 4 GiB through newbus (which propagates BUS_ALLOC_RESOURCE up to nexus) and later
releases or deactivates it (driver detach, kldunload, device hot-unplug, or any explicit
bus_deactivate_resource).
Proof of concept
Reproduction requires hardware with a >4 GiB MMIO BAR (e.g. a discrete GPU with Resizable BAR enabled in firmware) and the ability to force that driver to allocate then release the BAR.
PoC sketch:
- Boot DragonFlyBSD with
hw.pci.allow_resize_bars=1(or firmware already granting a large BAR) so the gpu/DRM driver attaches with a 4-8 GiB BAR. - Write a small kld module that, in its load handler, calls
bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE)for a PCI device whose BAR is >= 4 GiB, then in its unload handler callsbus_release_resourceβ or alternatively trigger drm driver detach/attach viadevctl. - After each cycle, inspect
vmstat -m/pmapof the kernel for unshrinking KVA usage in thekernel_mapand (via DDBshow ptesor a kernelprintfadded topmap_unmapdev) confirm the high PTEs above the truncated boundary are still present and RW.
Build & run
# Requires hardware with >4 GiB MMIO BAR + Resizable BAR enabled. # Trigger a driver alloc/release cycle as root: sudo kldload <driver> sudo kldunload <driver> # Repeat; observe KVA usage: vmstat -m | head # Or via DDB, examine kernel_pmap PTEs for the released region.
Expected output
kernel_map free space shrinks monotonically across cycles (eventual panic on kmem_alloc
for mapdev), and/or stale PTEs mapping the released physical MMIO range are observable.
No userspace syscall alone triggers this; the trigger is kernel-side driver resource
lifecycle, so the practical bar is local root (devctl / kldload) or physical access β
which is why the severity is Low.
Impact
KVA leak + stale kernel-pmap PTEs from a driver detach of a >4 GiB MMIO BAR. Local root or
physical access required. Low severity per "narrow trigger, requires specific hardware"
(AV:L/AC:H/PR:L/A:L).
Recommended fix
Widen psize in nexus_deactivate_resource to match the activate path exactly:
--- a/sys/platform/pc64/x86_64/nexus.c
+++ b/sys/platform/pc64/x86_64/nexus.c
@@ -459,7 +459,7 @@
* If this is a memory resource, unmap it.
*/
if ((rman_get_bustag(r) == X86_64_BUS_SPACE_MEM) &&
(rman_get_end(r) >= 1024 * 1024)) {
- u_int32_t psize;
+ u_int64_t psize;
psize = rman_get_size(r);
pmap_unmapdev((vm_offset_t)rman_get_virtual(r), psize);
This makes the deactivate size type identical to the activate size type (nexus.c:437) and
ensures pmap_unmapdev receives the full 64-bit size, so the entire originally-mapped KVA
range is qremove'd and freed.
Optional defense-in-depth: also add
KASSERT(rman_get_end(r) >= rman_get_start(r), ...) and KASSERT(psize > 0, ...) at the
top of the block to catch inverted / empty ranges before they reach pmap_unmapdev.
References
sys/platform/pc64/x86_64/nexus.c:437, 444βnexus_activate_resource(correct u_int64_t)sys/platform/pc64/x86_64/nexus.c:462-465βnexus_deactivate_resource(truncating u_int32_t)sys/sys/rman.h:103, 104, 146βu_long r_start / r_end,rman_get_sizereturns u_longsys/platform/pc64/x86_64/pmap.c:6223-6232βpmap_unmapdevuses the size verbatimsys/cpu/x86_64/include/types.h:40βvm_size_tis 64-bitsys/platform/pc64/x86_64/nexus.c:225βmem_rmanmanages[0, ULONG_MAX]- CWE-197 Numeric Truncation Error
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1074 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix for the cited path | 411 B | view raw |
| VERDICT.md | verdict | source-confirmation narrative | 913 B | β raw |
| env.txt | environment | guest uname + toolchain | 247 B | view raw |
DF-1074 source-confirmation
Verdict: REPRODUCED (source-confirmed) Impact: none Confidence: likely
Kernel ref: sys/platform/pc64/x86_64/nexus.c:462
Mechanism
nexus_deactivate_resource truncates MMIO size: u_int32_t psize truncates 64-bit rman_get_size -> stale PTEs + KVA leak for >4GiB BARs. local root/hotplug; confirmed.
Confirmation method
source-only Low-severity; confirmation by code inspection. Runtime PoC not exercised for this Low-severity item; confirmation is by code inspection against sys/.
Recommended fix
See fix.diff in this folder (git-apply-able unified diff).
Phase 8 (combined build)
This fix is part of the batched 70-finding combined patch
(../_batch70/combined_70.patch) applied to in-guest /usr/src. A single
make -j6 nativekernel KERNCONF=X86_64_GENERIC build is validated rc=0 with 0
errors under -Werror (../_batch70/fix_build.log).
Fix verification
fixedVALIDATED via combined build: fix in combined_70.patch; single make -j6 nativekernel built rc=0, 0 errors under -Werror (../_batch70/fix_build.log). Cited line corrected. Source-only -> validation = clean -Werror compile.
'>>> Kernel build for X86_64_GENERIC completed' + 'NK_DONE rc=0'; grep -cE 'error:|undefined reference' fix_build.log = 0
Confirmed kernel references
- s
- y
- s
- /
- p
- l
- a
- t
- f
- o
- r
- m
- /
- p
- c
- 6
- 4
- /
- x
- 8
- 6
- _
- 6
- 4
- /
- n
- e
- x
- u
- s
- .
- c
- :
- 4
- 6
- 2
Detail
Exploit chain
none (source-only Low finding, not memory-corruption driven to runtime; no escalation chain)
Evidence (decisive lines)
baseline (with-src #0): bug at sys/platform/pc64/x86_64/nexus.c:462. combined-70 fix kernel: NK_DONE rc=0 (0 errors, -Werror).
PoC changes
authored/validated fix.diff (findings/poc/DF-1074/fix.diff); part of combined_70 kernel build.
Verified recommended fix
See findings/poc/DF-1074/fix.diff (git-apply-able). Matches finding proposal.
Verdict
REAL: nexus_deactivate_resource u_int32_t psize truncates 64-bit rman_get_size -> stale PTEs/KVA leak for >4GiB BARs. root/hotplug. confirmed.
No comments yet.