# DF-5837 — cluster_wbuild maps the pbuf KVA window *before* validating it (and outside the KVABIO bkvareset protocol)

File: `sys/kern/vfs_cluster.c` (pass-3 fresh-perspective re-audit)

## What (code facts, all certain)

1. **Ordering inversion vs the read-side twin.**
   `cluster_wbuild()` installs the cluster KVA window first and bounds-checks
   second:

   - `sys/kern/vfs_cluster.c:1691-1693` — `pmap_qenter_noinval(trunc_page(bp->b_data), bp->b_xio.xio_pages, bp->b_xio.xio_npages);`
   - `sys/kern/vfs_cluster.c:1694-1698` — only *afterwards*: `if (bp->b_bufsize > bp->b_kvasize) panic(...)`

   `cluster_rbuild()` does it the safe way around — validate at
   `sys/kern/vfs_cluster.c:1094-1097`, then map at `1098-1100`.

2. **First component exempt from the page-count guard.**
   The per-component bound
   (`(tbp->b_xio.xio_npages + bp->b_xio.xio_npages) > (maxiosize / PAGE_SIZE)`,
   `sys/kern/vfs_cluster.c:1606-1616`) is evaluated only on the `i > 0` path;
   the `i == 0` component's pages are appended unconditionally
   (`sys/kern/vfs_cluster.c:1636-1668`). Non-VMIO components additionally
   accumulate `bp->b_bcount/bp->b_bufsize` (`1670-1671`) without appending any
   pages — today unreachable because getblk forces B_VMIO
   (`sys/kern/vfs_bio.c:2810`, `2974`), but the guard does not enforce it.

3. **Pbuf window never enters the KVABIO cpumask protocol.**
   rbuild/wbuild set `B_KVABIO` on the pbuf (`sys/kern/vfs_cluster.c:940`,
   `1551`) and `pmap_qenter_noinval()` stores PTEs with **no TLB
   invalidation** (`sys/platform/pc64/x86_64/pmap.c:2164-2166`), but neither
   site calls `bkvareset(bp)`. Every `pmap_qenter_noinval` site in
   `sys/kern/vfs_bio.c` pairs with `bkvareset()` (`vfs_bio.c:3216`, `3838`,
   `3994`); `initpbuf()` (`sys/vm/vm_pager.c:382-395`) also never resets
   `b_cpumask`, so a kva-pbuf's mask is carried across recycling.

## Why it matters (latent impact)

The pbuf KVA arena is one contiguous `nswbuf_kva * MAXPHYS` region
(`sys/vm/vm_pager.c:239-242`); each pbuf window is MAXPHYS. If component
accounting ever lets `xio_npages`/`b_bufsize` exceed the window — precisely
the defect class proven on the read side by DF-2725 (component b_bufsize
exceeding blksize under-counts pages vs the xio_pages[] array/KVA window) —
then on the write side the out-of-bounds PTEs are actually **installed into
the adjacent pbuf's window first**, cross-mapping the next pbuf's I/O to the
wrong physical pages, and only then does the panic fire. The rbuild ordering
(panic first) contains exactly this scenario to a clean panic; wbuild does
not.

The stale-cpumask half is currently benign *by luck*: nothing in the
DMA strategy path calls `bkvasync()` on the cluster pbuf (nvme/cam build
SGLs via `vtophys`/`pmap_kextract`, which walk page tables — TLB-immune:
`sys/dev/disk/nvme/nvme.c:390`, `sys/platform/pc64/x86_64/pmap.c:1865-1890`),
so the pbuf mask stays 0 and any future correct `bkvasync()` caller would
invalidate properly. It becomes live the moment any CPU-touching consumer
(bcopy-style driver, in-kernel copy path) participates — see the md(4)
observation in the audit notes (md.c bcpopies through `bp->b_data` with no
`bkvasync` at all — a cross-file defect worth its own audit of
`sys/dev/disk/md/md.c`).

## Reachability today (why Low / not verified on the guest)

The exact page-count guard at `1609-1610` (unlike rbuild's round_page(blksize)
estimate) plus forced-B_VMIO buffers plus `vmaxiosize()` clamped to MAXPHYS
(`sys/kern/vfs_subr.c:1519-1531`) make both halves unreachable without a
second kernel bug:
- unique page tiling of contiguous components bounds `b_bufsize <=
  npages*PAGE_SIZE <= MAXPHYS = b_kvasize`;
- `xio_pages[]` holds `XIO_INTERNAL_PAGES == btoc(MAXPHYS) == 32` entries and
  the guard caps cumulative pages at `maxiosize/PAGE_SIZE <= 32`.

Therefore no unprivileged (or any) local program can trigger this on a
default-config guest; Phase V verification is intentionally skipped and the
finding stands as defense-in-depth/hardening with a concrete fix.

## Fix

Validate before mapping, and bring the pbuf window into the bkvareset
protocol (both build functions). See `fix.diff`.
