# DF-5801 VERDICT — vm_page_bits() bounds trust in release kernels

**Status: reproduced (hardening demonstration; no security impact demonstrated — Info)**
**Impact: none** (all in-tree callers currently bounded)
**Confidence: certain** (code-level claim proven on source and on the guest)

## What was tested

Pass-3 re-audit of sys/vm/vm_page.c (4244 LOC) flagged the valid/dirty
bit-mask core as the one remaining unvalidated arithmetic family:

- `vm_page_bits()` sys/vm/vm_page.c:3905-3923 — guard is
  `KASSERT(base + size <= PAGE_SIZE)`, compiled out in release builds
  (sys/sys/systm.h:116-119 `#else #define KASSERT(exp,msg) do { } while (0)`).
- Consumers: `vm_page_set_valid` (:3999), `vm_page_set_validclean` (:4018),
  `vm_page_set_validdirty` (:4038), `vm_page_clear_dirty` (:4060),
  `vm_page_set_invalid` (:4102, itself separately DF-0942 for the NULL
  object), `vm_page_is_valid` (:4169), and int-sized mask compares in
  kern/vfs_bio.c:4100.

## How it was verified

1. **KLD rig on the live audit guest** (`pqb_bits_rig.c`): calls the real
   exported `vm_page_bits()` with (0, PAGE_SIZE*2). Result: kernel panic
   `vm_page_bits: illegal base/size 0/8192` with the rig's exact arguments
   in the message and `rig_loader` in the backtrace — i.e. the input
   reached the function with no validation anywhere upstream. Reproduced
   on two separate boots (one after a full `vm.sh reset with-src`).
   panic.txt holds both traces. The guest kernel is an INVARIANTS build
   (guest config /usr/src/sys/config/X86_64_GENERIC line 56
   `options INVARIANTS`), so the assert fired there.

2. **Release semantics** (`bitsarith.c`, guest cc 8.3 -O2 — same compiler
   family as the kernel): with the assert compiled out, the identical
   expression yields
   - bits(0,8192)        = 0xFFFF  (OR into uint8_t valid -> whole page
     "valid": over-validation / stale-data direction)
   - bits(0,4608)        = 0x1FF   (partial over-validation)
   - bits(4096,1)        = 0x100   (phantom bit 8, visible to int
     consumers like vfs_bio.c:4100)
   - bits(-512,1024)     = UB (x86: 0x2 here; standard C: undefined)
   - bits(0,1<<30)       = UB (x86: 0xFFFFFFFF via signed overflow)

3. **Caller sweep (negative result with weight)**: every in-tree caller of
   the setter family and vm_page_bits was traced and found to bound
   base+size <= PAGE_SIZE by construction:
   - kern/uipc_syscalls.c:1771-1807 (sendfile): xfsize = min(PAGE_SIZE -
     pgoff, nbytes - xbytes), and `if (xfsize <= 0) break`.
   - kern/vfs_helper.c:346-354: constant (0, PAGE_SIZE).
   - vm/vm_fault.c:3328-3330: constant (0, PAGE_SIZE).
   - kern/vfs_bio.c:377 (vfs_buf_test_cache): fragment-sized.
   - kern/vfs_bio.c:2510-2517: tinc clamped to PAGE_SIZE - (off & PAGE_MASK).
   - kern/vfs_bio.c:4100/4149 (vfs_clean_one_page): soff/eoff walk the
     buffer page-by-page.
   - vfs/devfs/devfs_vnops.c:2206-2216: guarded by `toff < nread <= bcount`.
   - vfs/smbfs/smbfs_io.c:494: nvalid from bounded getpages geometry.
   Hence Info severity: the primitive exists, no current caller trips it.

## Why not higher severity

The over-validation direction is an information-disclosure *primitive*
(mark never-populated fragments valid so userland can read stale page
contents), but triggering it requires a caller that passes an
out-of-page range; the sweep above found none. No user-reachable
miscalculation exists today, so the finding is filed as hardening.

## Exploit chain

(none — no in-tree attacker-reachable path)

## Fix

`fix.diff` clamps base/size to the page unconditionally before the
(existing) KASSERT: kills the shift UB, kills phantom bits >= 8, and
makes the mask semantically page-bounded for every consumer.

Fix validation performed on the guest (see fix_run.log / VERDICT section
in manifest): single-fix kernel built with `make nativekernel
KERNCONF=X86_64_GENERIC` after applying fix.diff in the guest /usr/src;
rig reloaded: **no panic**, `vm_page_bits(0,8192)` now returns 0xFF
(clamped full-page), `vm_page_bits(4096,1)` returns 0x0, UB inputs
clamped to sane in-page masks, guest stays up (`kldstat` shows the rig
loaded). Baseline (same rig, unpatched kernel) panicked twice.

## Kernel references

- sys/vm/vm_page.c:3905-3923 (vm_page_bits)
- sys/vm/vm_page.c:3940-4003 (_vm_page_zero_valid / vm_page_set_valid)
- sys/vm/vm_page.c:4017-4030, 4037-4047, 4059-4067, 4101-4110 (setters)
- sys/sys/systm.h:90-119 (KASSERT/KKASSERT INVARIANTS-only)
- sys/vm/vm_page.h:197-198 (uint8_t valid / dirty)
- kern/vfs_bio.c:4100 (int-sized mask consumer)

## Guest

DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 (INVARIANTS
kernel config); guest reset to clean-source snapshot after verification.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
