Leftover DEBUG kprintf in vm_pageout_scan_hold leaks kernel pointer and spams the console on the normal race-recovery path
| Field | Value |
|---|---|
| ID | DF-2689 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-532 Sensitive Information in Log |
| File | sys/vm/vm_pageout.c |
| Lines | 1932 |
| Area | vm |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
vm_pageout_scan_hold walks PQ_HOLD to clean the benign
vm_page_free/vm_page_unhold race residue (documented at
vm_page.c:1403-1410). When it finds one it unconditionally prints
kprintf("DEBUG: pageout HOLD->FREE %p\n", m) before the reprocess.
That residue case is the scanner's expected input, so every occurrence
emits a raw vm_page_t pointer to the console/msgbuf β
unprivileged-triggerable log spam under ordinary memory pressure and
kernel heap-pointer disclosure (KASLR-defeating on hardened
deployments; Info on current DF which has no KASLR).
Recommended fix
Delete the line (git-apply-clean diff in findings/poc/DF-2689/).
Timeline
- 2026-08-30 Discovered during pass-2 audit of vm_pageout.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2689 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| fix.diff | β | 265 B | view raw | |
| verdict.json | β | 1.6 KB | view raw |
DF-2689 β Leftover DEBUG: kprintf in vm_pageout_scan_hold leaks kernel pointers and spams the console on a normal race-handling path
File: sys/vm/vm_pageout.c:1932 (vm_pageout_scan_hold).
Status
UNTESTED (Info β hardening). Source-evident; no runtime demonstration
needed or attempted (guest left clean). The print fires on the handled
PQ_HOLD race residue path (page left on PQ_HOLD with hold_count == 0 by
the documented vm_page_free/vm_page_unhold race,
sys/vm/vm_page.c:1403-1410), i.e. every occurrence prints a raw vm_page_t
pointer and a per-occurrence line to the console/message buffer.
What it is
if (m->hold_count)
break;
kprintf("DEBUG: pageout HOLD->FREE %p\n", m); /* <-- :1932 */
vm_page_hold(m);
...
vm_page_unhold(m); /* reprocess */
Impact: (1) unprivileged-triggerable console/log spam on a nominally silent recovery path (the underlying race is driven by ordinary memory pressure); (2) kernel heap-pointer disclosure to any local user who can read the message buffer (dmesg), a KASLR-defeating primitive on kernels that use KASLR and needless information exposure otherwise. Severity: Info on current DragonFly (no KASLR by default).
Fix
Delete the line (see fix.diff). The hold/unhold "reprocess" dance itself was
re-verified correct against vm_page_unhold's re-test protocol
(sys/vm/vm_page.c:1413-1432) β pages parked on PQ_HOLD have already been
disassociated from their object (vm_page_free_toq β vm_page_remove,
sys/vm/vm_page.c:3186), no stray holders can exist, and the final unhold
re-checks under both spinlocks before moving the page to PQ_FREE.
DF-2689 VERDICT β leftover DEBUG kprintf in vm_pageout_scan_hold
Status: untested (Info hardening finding; source-certain, no guest run needed).
Root cause
sys/vm/vm_pageout.c:1932 β an unconditional kprintf("DEBUG: pageout
HOLD->FREE %p\n", m) sits on the normal recovery path of
vm_pageout_scan_hold(). The scanner exists to clean the benign race
residue documented in vm_page_unhold() (sys/vm/vm_page.c:1403-1410): a
vm_page_free*() moving a held page to PQ_HOLD can interleave with the
holder's final unhold, leaving the page on PQ_HOLD with hold_count == 0.
The scanner detects exactly that case and "reprocesses" it with a
hold/unhold cycle β that case is the expected input of this scanner, yet
it prints a debug line with a raw kernel pointer each time.
Verification of the surrounding logic (cross-check requested by the audit)
- The holdβunhold "reprocess" is safe:
vm_page_hold()only asserts the page is not on PQ_FREE (vm_page.c:1392-1396); a PQ_HOLD page withhold_count == 0andm->object == NULL(already removed byvm_page_free_toqβvm_page_remove, vm_page.c:3186) has no possible concurrent legitimate holder, and the finalvm_page_unhold()re-testshold_count == 0 && queue == PQ_HOLDunder the page and queue spinlocks before moving it to PQ_FREE (vm_page.c:1420-1428). - The queue-spinlock drop/re-acquire around the unhold (:1934-1936) is
required because unhold may take the page and queue spinlocks itself;
no marker protocol violation results (marker advanced past
mbefore unlocking; PG_MARKER pages skipped at :1924). - Conclusion: logic correct, print is pure leftover debug output.
Impact
Info. Console/message-buffer spam on a path reachable from unprivileged memory-pressure workloads, plus kernel pointer disclosure (CWE-532) to local users via readable message buffer; pointer disclosure matters mainly as a KASLR defeat on hardened deployments.
Why not run on the guest
The finding is a static one-line defect; the PQ_HOLD residue race that triggers the print is rare and non-deterministic, and demonstrating log spam adds no evidence beyond the source itself. Guest left untouched.
Fix
fix.diff: remove the kprintf. (Alternative if ever desired for triage:
gate it behind vm_pageout_debug like the other debug prints in this file.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
['sys/vm/vm_pageout.c:1932 (the DEBUG print)', 'sys/vm/vm_page.c:1403-1410 (documented free/unhold race this scanner cleans)', 'sys/vm/vm_page.c:1420-1428 (unhold re-test under spinlocks β scanner dance verified safe)', 'findings/poc/DF-2689/fix.diff (delete the line)']
Verified recommended fix
Delete the kprintf at vm_pageout.c:1932 (or gate it behind vm_pageout_debug).
Verdict
Source-certain Info finding: an unconditional leftover kprintf at sys/vm/vm_pageout.c:1932 prints 'DEBUG: pageout HOLD->FREE
No comments yet.