DF-5815 / verdict.json
{ "finding_id": "DF-5815", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Deterministically reproduced (2/2 runs). With root-set vm.max_launder=-32, vm_pageout_scan_inactive()s budget math (sys/vm/vm_pageout.c:828-836) yields a NEGATIVE per-queue max_launder (-32/64=0 -> floor 1 -> cap test 1 >= -2 always true -> -2+1 = -1), so vm_pageout_page()s laundering branch (:1171, *max_launderp > 0) is permanently unreachable: dirty pages are never paged out. An unprivileged 3.4GB anonymous hog (uid 1001) then exhausts free+cache; the pagedaemon spins forever (pagedaemon: WARNING! Continuous paging for 1 minutes every minute on the serial console, 5x in run #1 / 3x in run #2), all vm_wait() sleepers block, ssh dies with Connection timed out during banner exchange, and only a hard reset recovers (both runs ended in vm.sh reset). Identical hog with stock vm.max_launder=4076: 39047 swap pager pageouts, hog completes in 61s, ssh alive, zero warnings. Severity Low only because the enabling write requires root; after the misconfiguration any unprivileged user freezes the machine and remote administration is lost until power-cycle.", "exploit_chain": "root (or a management agent) writes sysctl vm.max_launder=-32 -> per-queue launder budget negative -> pagedaemon can never clean dirty pages -> unprivileged user runs ordinary memory hog -> free+cache exhausts -> all allocations block in vm_wait forever -> console floods Continuous paging once/min -> ssh/remote admin dead -> hard reset required. No privilege escalation path (governor state only).", "evidence": [ "findings/poc/DF-5815/control_vs_attack.txt \u2014 side-by-side: stock=39047 swap pageouts/hog completes; -32=frozen laundering + wedge", "findings/poc/DF-5815/panic_wedge_console.txt \u2014 run #1 serial console: 5x 'pagedaemon: WARNING! Continuous paging for 1 minutes'", "findings/poc/DF-5815/attack2_console.txt \u2014 run #2 (deliberate): 3x warning, ssh dead by t+170s", "findings/poc/DF-5815/observe_attack.log \u2014 swap pageouts frozen at 205 with -32 while fre sits at 80MB", "sys/vm/vm_pageout.c:828-836 \u2014 the unvalidated-division + cap inversion", "sys/vm/vm_pageout.c:1171 \u2014 '*max_launderp > 0' branch never taken" ], "kernel_refs": [ "sys/vm/vm_pageout.c:828", "sys/vm/vm_pageout.c:836", "sys/vm/vm_pageout.c:1171", "sys/vm/vm_pageout.c:2707" ], "poc_changes": "none \u2014 the hog is a plain anonymous-memory touch loop; the decisive parameter (3400MB on 3.4GB usermem) was found by escalation from 2900MB (which left enough free+cache headroom for the daemon to idle at target).", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 900, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o df5815_hog df5815_hog.c", "run_cmd": "root# sysctl vm.max_launder=-32 ; user$ ./df5815_hog 3400 60 ; watch serial console + try ssh", "code_hash": "3182b74e004baa3c537a572affc3a0a86fa953be2f6cc291ff0811e79213ca28", "notes": "Guest reset with-src after each wedge run; final state clean. Run #1 was initiated as a control before realizing the -32 setting had not been restored \u2014 its capture is serendipitous but complete (console + observe logs). The same unvalidated-input class applies to several other governor sysctls in this file (vm.pageout_stats_scan, vm.emerg_launder, vm.pageout_stats_inalim); max_launder is the only one whose mis-sign disables a kernel subsystem outright.", "recommended_fix": "Clamp vm_max_launder at the use site (negative -> 0) and make the cap conditional; see fix.diff.", "fix_status": "not_tested", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (negative clamp + cap-condition fix) applies clean to sys/vm/vm_pageout.c and provably removes the negative-budget path by construction; kernel fix-build not performed (non-corruption Low/DoS finding; guest kernel rebuild ~40min not justified per contract). Baseline (misconfig+wedge) reproduced 2/2 before the fix.", "fix_evidence": "findings/poc/DF-5815/fix.diff (git apply --check clean)" } |