cluster_wbuild installs the pbuf KVA window before validating it, with the first component exempt from the page guard and the window outside the bkvareset() protocol
Summary
cluster_wbuild qmaps the cluster window with pmap_qenter_noinval and only then checks b_bufsize > b_kvasize β inverting the safe order cluster_rbuild uses (panic first, map second); the per-component page bound runs only for i>0; and both build functions set B_KVABIO on the pbuf without ever calling bkvareset() after qentering, unlike every qenter site in vfs_bio.c, while initpbuf never resets b_cpumask. Latent memory-corruption amplifier: today wbuild's exact page guard plus forced-B_VMIO buffers plus vmaxiosize<=MAXPHYS keep npages<=32 β but if component accounting ever slips the way DF-2725 proved on the read side, the write side performs the OOB PTE install into the adjacent pbuf's contiguous KVA window (cross-mapping a neighbor's in-flight I/O to wrong physical pages) before the panic fires; any future CPU-touching window consumer inherits stale-TLB wrong-page access. No trigger on default config (honest untested). Hoist-the-panic + bkvareset fix git-apply-clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5837 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.1 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| fix.diff | β | 1.7 KB | view raw | |
| verdict.json | β | 3.8 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
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)
- 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.
-
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 thei > 0path; thei == 0component's pages are appended unconditionally (sys/kern/vfs_cluster.c:1636-1668). Non-VMIO components additionally accumulatebp->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. -
Pbuf window never enters the KVABIO cpumask protocol. rbuild/wbuild set
B_KVABIOon the pbuf (sys/kern/vfs_cluster.c:940,1551) andpmap_qenter_noinval()stores PTEs with no TLB invalidation (sys/platform/pc64/x86_64/pmap.c:2164-2166), but neither site callsbkvareset(bp). Everypmap_qenter_noinvalsite insys/kern/vfs_bio.cpairs withbkvareset()(vfs_bio.c:3216,3838,3994);initpbuf()(sys/vm/vm_pager.c:382-395) also never resetsb_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.
DF-5837 β VERDICT
status: untested (deliberately not executed β latent defect, unreachable without a second kernel bug) impact: none demonstrated (latent OOB-PTE/cross-pbuf-mapping hardening) confidence: certain (code facts), speculative (exploitability)
What was verified statically (line-accurate)
-
sys/kern/vfs_cluster.c:1691-1698βpmap_qenter_noinval()runs BEFORE thebp->b_bufsize > bp->b_kvasizepanic check incluster_wbuild(). Differential:cluster_rbuild()atsys/kern/vfs_cluster.c:1094-1100panics first, maps second. If the invariant ever breaks (the DF-2725 defect class: componentb_bufsize > blksizeunder-counting pages), wbuild installsxio_npagesPTEs starting attrunc_page(bp->b_data)(the pbuf's KVA base,sys/vm/vm_pager.c:299-301β contiguous arenanswbuf_kva * MAXPHYSatsys/vm/vm_pager.c:239-242) and then detects the overrun. Overflowing PTEs land in the next pbuf's window, so the neighbor's in-flight I/O gets cross-mapped before the panic stops the system. -
The wbuild per-component page guard (
sys/kern/vfs_cluster.c:1606-1616) uses actualxio_npages(unlike rbuild'sround_page(blksize)estimate at956-958) β so wbuild does NOT have DF-2725's counting flaw β but it only runs fori > 0; the first component (frombppmatch at1475-1478or the outerfindblkat1480-1492) is appended unguarded (1636-1668). With MAXBSIZE = 64KB (sys/sys/param.h:372) a first component contributes β€ 17 pages (allocbufmathsys/kern/vfs_bio.c:3045-3047), so β€ 32 (XIO_INTERNAL_PAGES,sys/sys/xio.h:66) β safe today, fragile tomorrow. -
Neither
cluster_rbuild(sys/kern/vfs_cluster.c:1098) norcluster_wbuild(:1691) callsbkvareset(bp)after qentering, although both setB_KVABIO(:940,:1551) andpmap_qenter_noinvalperforms no TLB invalidation (sys/platform/pc64/x86_64/pmap.c:2164-2166).initpbuf()(sys/vm/vm_pager.c:382-395) never resetsb_cpumask, so the mask persists across pbuf recycling. Every qenter site insys/kern/vfs_bio.c(3216,3838,3994) pairs withbkvareset().
Why not executed on the guest
- Triggering half 1 requires breaking the exact page/bufsize accounting β i.e. requires a second, unknown kernel bug (DF-2725 is read-side-only; wbuild's exact guard closes it on the write side).
- Triggering half 3 requires a CPU-touching (bcopy) consumer of the cluster
window; none exists in the default strategy chain (nvme/cam derive
physical addresses via
vtophys()βpage-table walk,sys/dev/disk/nvme/nvme.c:390,sys/platform/pc64/x86_64/pmap.c:1873-1887β TLB-immune). The one in-tree bcopy consumer that could be paired with clustered file I/O ismd(4)(sys/dev/disk/md/md.c:254-333, bcopy/bzero throughbp->b_datawith no bkvasync) β a defect in that file, filed here only as a cross-reference note, not as part of this finding.
Guest reference (idle, not dirtied): DragonFly dfbsd 6.5-DEVELOPMENT #0 X86_64_GENERIC.
Recommended fix
fix.diff in this directory: (a) hoist the kvasize check above the qenter in
cluster_wbuild (mirroring rbuild), (b) bkvareset(bp) after both qenter
sites so the window re-enters the KVABIO protocol. Fix not built/validated on
the guest (nothing observable changes without a triggering bug;
fix_status: not_testable).
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.
Fix verification
not_testablefix.diff authored against the read-only sys/ tree (guard hoist + bkvareset x2); not built on the guest because the guarded condition is unreachable on a stock kernel - no observable behavior changes to measure (fix_status not_testable).
findings/poc/DF-5837/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-5837/VERDICT.md (full path:line analysis); findings/poc/DF-5837/fix.diff (hoisted guard + bkvareset at both qenter sites); key lines sys/kern/vfs_cluster.c:1691-1698 vs 1094-1100, sys/kern/vfs_cluster.c:1606-1616 (i>0-only guard), sys/kern/vfs_cluster.c:940+1551 (B_KVABIO), sys/vm/vm_pager.c:239-242+299-301+382-395, sys/kern/vfs_bio.c:3216+3838+3994 (qenter/bkvareset pairing), sys/platform/pc64/x86_64/pmap.c:2164-2166 (qenter_noinval semantics)
PoC changes
no PoC built - latent defect not reachable by any local program on a default-config guest without a second kernel bug; verification intentionally skipped per contract for Low hardening findings
Verified recommended fix
Hoist the b_bufsize>b_kvasize panic above pmap_qenter_noinval in cluster_wbuild (mirror cluster_rbuild) and call bkvareset(bp) after both qenter sites so the pbuf window re-enters the KVABIO cpumask protocol.
Verdict
Code defect confirmed by line-accurate static analysis: cluster_wbuild() installs the pbuf KVA window with pmap_qenter_noinval (sys/kern/vfs_cluster.c:1691-1693) BEFORE the b_bufsize>b_kvasize guard (1694-1698), inverting the safe ordering used by cluster_rbuild (1094-1100), and neither build function calls bkvareset() after qentering although both set B_KVABIO (940, 1551) and initpbuf never resets b_cpumask (sys/vm/vm_pager.c:382-395). Latent impact: any future component-accounting slip of the DF-2725 class turns into an actual out-of-bounds PTE install into the adjacent pbuf's contiguous KVA window (arena at sys/vm/vm_pager.c:239-242) before the panic fires; the stale cpumask becomes live for any CPU-touching window consumer. Not executed on the guest: unreachable without a second kernel bug - wbuild's exact per-component page guard (1606-1616, actual xio_npages), getblk forcing B_VMIO on all buffers (sys/kern/vfs_bio.c:2810,2974), vmaxiosize clamped to MAXPHYS (sys/kern/vfs_subr.c:1519-1531) and xio_pages[] sized btoc(MAXPHYS)=32 (sys/sys/xio.h:66) close every reachable path; the default disk strategy chain only derives physical addresses via TLB-immune page-table walks (sys/dev/disk/nvme/nvme.c:390, sys/platform/pc64/x86_64/pmap.c:1873-1887). Defense-in-depth fix authored (check-before-map + bkvareset).
No comments yet.