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

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).

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2689 Β· 4 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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 with hold_count == 0 and m->object == NULL (already removed by vm_page_free_toq β†’ vm_page_remove, vm_page.c:3186) has no possible concurrent legitimate holder, and the final vm_page_unhold() re-tests hold_count == 0 && queue == PQ_HOLD under 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 m before 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_testable
↓ fix.diffper-fix-DF-2689

Confirmed 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 ' on the PQ_HOLD scanner's normal, expected recovery path, disclosing a kernel heap pointer to the console/message buffer (CWE-532) and spamming the log under ordinary unprivileged memory pressure. The surrounding hold/unhold 'reprocess' logic was re-verified correct against vm_page_unhold's spinlocked re-test protocol (vm_page.c:1413-1432) and vm_page_free_toq's object disassociation (vm_page.c:3186); only the print is wrong. No guest run: static one-line defect, trigger race is rare and demonstrates nothing beyond the source.