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).
Recommended fix
--- 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).
No comments yet.