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

vm_page_free_contig() can leak PG_FICTITIOUS DMA-reserve pages when vm_low_phys_reserved has been lowered below the freed block

Field Value
ID DF-2678
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L
CWE CWE-401 Missing Release of Memory
File sys/vm/vm_page.c
Lines 2868 (mutable watermark branch), 3184 (fictitious early-return)
Area vm
Confidence speculative
Discovered 2026-08-29
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

vm_page_free_contig() decides between "return to alist" and "normal free" using pa < vm_low_phys_reserved (vm_page.c:2868), but vm_low_phys_reserved is a mutable watermark lowered by vm_page_startup_finish() (:826) as boot-time reserve pages are released. A contig block still inside the alist but above the final watermark takes the else-branch on a PG_FICTITIOUS page: vm_page_unwire is a no-op for fictitious pages and vm_page_free_toq early-returns at :3184-3188 β€” the page is permanently leaked from both the alist and the page queues, and v_wire_count accounting drifts.

Threat model & preconditions

In-kernel contigmalloc/contigfree users (drivers, privileged) on boot geometries where alist blocks survive above the final watermark; repeated alloc/free cycles shrink the low-DMA reserve until contigmalloc failures. No memory corruption; resource-exhaustion DoS of the DMA reserve only. Not reproduced (requires specific boot memory geometry; speculative).

--- a/sys/vm/vm_page.c
+++ b/sys/vm/vm_page.c
@@ -2865,7 +2865,7 @@ vm_page_free_contig(vm_page_t m, unsigned long size)
    vm_pindex_t start = pa >> PAGE_SHIFT;
    vm_pindex_t pages = (size + PAGE_MASK) >> PAGE_SHIFT;

-   if (pa < vm_low_phys_reserved) {
+   if (m->flags & PG_FICTITIOUS) {
        /*
         * Just assert check the first page for convenience.
         */

Timeline

  • 2026-08-29 Discovered during pass-2 audit of vm_page.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2678 Β· 3 files
FileTypeDescriptionSize
v26harness.c β€” 14.3 KB view raw
run.log β€” 555 B view raw
fix.diff β€” 950 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel: vm_page_free_contig now returns fictitious pages to the DMA alist regardless of the watermark; the harness exhaustion loop ran to its 8192-iteration cap (baseline died at 2046 = full 141MB reserve lost) proving contigmalloc below the watermark never fails again.

cmd=4 on patched kernel: exhaustion loop: 8192 x 64KB (cap reached, no failure); free/wire counters stable
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 24 17:14:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (v26 fix-all kernel; sys_generic.c fix-v2 rebuild pending A/B)

Confirmed kernel references

Detail

Exploit chain

kernel harness (root kldload) -> contigmalloc below watermark -> force watermark 0 -> vm_page_free_contig -> fictitious early-return leak -> repeat 2046x -> DMA reserve exhausted

Evidence (decisive lines)

v26harness.c t4 (cmd=4); df2678_mod.log: LEAK lines + exhaustion loop; kernel refs vm_page.c:2868,3184-3188,826

PoC changes

none (first-version harness)

Verified recommended fix

--- sys/vm/vm_page.c 2026-06-29 12:51:19.000000000 +0000

Verdict

Mechanically reproduced with a kernel-module harness that injects the exact precondition the finding describes: a PG_FICTITIOUS page allocated from the DMA alist whose physical address sits above vm_low_phys_reserved (the geometry vm_page_startup_finish produces when it lowers the watermark below blocks still tracked by the alist). vm_page_free_contig (sys/vm/vm_page.c:2868) branched on pa < vm_low_phys_reserved, took the normal-free path, and vm_page_free hit the PG_FICTITIOUS early-return at vm_page.c:3184-3188: the page was returned to neither the alist nor the free queues. A loop of alloc/free cycles drained 2046 x 64KB (128MB) from the guests 141MB DMA reserve before contigmalloc below the watermark failed permanently. Guest boot geometry showed the real watermark at 0xb063000 with 36163 reserved dma pages, so the scenario is boot/driver-geometry dependent exactly as the finding states, but the branch defect is proven deterministically. No unprivileged trigger: contigmalloc/free_contig callers are in-kernel.