DF-5812 / verdict.json
{ "finding_id": "DF-5812", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "swaponvp() (sys/vm/vm_swap.c:365-366) rounds the swap device page count UP to the SWB_DMMAX (64-page) stripe boundary and frees the full padded stripes into the swap blist (vm_swap.c:377-383). For any swap device whose size is not a multiple of 256KB, 1..63 pages of non-existent phantom swap are published (vm_swap_size/swapinfo overstate the device; the strategy-layer bound check at vm_swap.c:128 uses the padded sw_nblks). Under unprivileged memory churn, pageout allocations reach the phantom tail; the OOB swap write is rejected by the backend (EINVAL) and the write-error path (swap_pager.c:1965-1994 -> swp_pager_freeswapspace at swap_pager.c:583-603) frees the phantom block back to the blist and redirties the page, so the pageout daemon reallocates the same phantom block forever: the guest wedged in an infinite swap_pager: I/O error - pageout failed; offset 3444736 ... error 22 loop (interleave page 841 = vn0 device page 201 = first phantom page of a 200-page device) and required a hard reset. On backends that do not bounds-check past-EOF I/O the same bug is a silent OOB write of page data past the end of the swap device.", "exploit_chain": "admin swapon on an unaligned-size device (e.g. odd-sized swap file/partition) -> blist contains phantom free blocks -> any local users anonymous-memory churn drives pageout into the phantom tail -> backend rejects OOB write (EINVAL) -> error handler frees phantom block back to blist + redirties page -> pageout reallocates identical block -> infinite retry livelock; console spam at line rate; system unresponsive (ssh dead); hard reset required. Ceiling: DoS (demonstrated) / silent cross-partition write on non-checking backends.", "evidence": "run.log steps 0-3: vm.swap_size delta +192 pages on swapon of a 200-page vn0 and swapinfo showing /dev/vn0 2048 512-blocks (1MB) for an 800KB file; hang-serial-console.txt: infinite swap_pager: I/O error - pageout failed; offset 3444736, size 65536, error 22 (page 841 -> seg 13 -> device slot 1 vn0 -> device page 201 = phantom) with the guest wedged; fix_run.log: same PoC on the patched kernel shows +128 pages, swapinfo 1536 512-blocks, file unchanged, clean churn and swapoff.", "kernel_refs": [ "sys/vm/vm_swap.c:365", "sys/vm/vm_swap.c:367", "sys/vm/vm_swap.c:377", "sys/vm/vm_swap.c:128", "sys/vm/swap_pager.c:537", "sys/vm/swap_pager.c:583", "sys/vm/swap_pager.c:1965" ], "poc_changes": "No seed PoC existed; authored fresh: churn.c (anonymous malloc+touch helper) and run.sh (200-page vn swap file, swapon, swapinfo/sysctl capture, 4600MB churn, file-size evidence, teardown). First run attempt aborted on sysctl vm.swap_max being unexported on this kernel; script hardened to tolerate it.", "attempts": 3, "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 (baseline) / #1: Tue Oct 13 02:09:45 UTC 2026 (patched)", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cc -O2 -o /root/churn /root/churn.c", "run_cmd": "sh /root/run.sh (creates 200-page /root/swapfile, vnconfig -c vn0, swapon /dev/vn0, /root/churn 4600, evidence, swapoff/teardown)", "code_hash": "346c96b075be194c01d769845ac858b819ad4fb89278758897f8261a6701928f", "notes": "Memory-corruption-class manifestation is the OOB swap write target past device EOF (bounded to <=63 pages/device, blist-chosen not attacker-chosen); on the vn file backend it surfaces as EINVAL + livelock rather than silent corruption, so the demonstrated impact bucket is dos. MB-aligned default installs are unaffected (sizes multiple of 256KB). Not a uid0 vector: write offset/content are not attacker-directed.", "recommended_fix": "In swaponvp() round the device size DOWN to the stripe boundary instead of UP: aligned_nblks = nblks & ~SWB_DMMASK (see fix.diff).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (single line: round down) applied to the guest /usr/src, kernel rebuilt via make nativekernel (full log fix_build.log), installed and rebooted (kernel #1 Oct 13 02:09:45). Identical PoC re-run: vm.swap_size delta +128 (was +192), swapinfo vn0 1536 512-blocks (was 2048), backing file unchanged after the identical 4600MB churn (was wedged in the error loop), swapoff succeeded. Every baseline artifact of the bug is gone; fix_patched_reproduced=0 means the bad behavior did NOT reproduce on the patched kernel.", "fix_evidence": "fix_run.log (full patched run, steps 0-5 complete with NOT-REPRODUCED marker and clean teardown); fix_build.log (full nativekernel build); VERDICT.md baseline-vs-patched table." } |