β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1074

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_map proportional to the un-unmapped upper fragment, compounding across cycles and eventually capable of exhausting kernel virtual address space (DoS / panic on kmem_alloc failure).
  • Stale PTEs in kernel_pmap that 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 later kmem_alloc aliasing 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:

  1. 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.
  2. 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 calls bus_release_resource β€” or alternatively trigger drm driver detach/attach via devctl.
  3. After each cycle, inspect vmstat -m / pmap of the kernel for unshrinking KVA usage in the kernel_map and (via DDB show ptes or a kernel printf added to pmap_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).

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

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1074 Β· 3 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-confirmation narrative
↓ download 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/.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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
↓ fix.diffDragonFly 6.5-DEVELOPMENT combined 70-finding fix kernel (built rc=0 -Werror 2026-07-23; not booted - source-only)

Confirmed kernel references

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.