cluster_rbuild() under-counts component pages vs the pbuf's embedded xio_pages[]/KVA window β latent OOB pointer write + PTE install past MAXPHYS window for non-page-tiling VFS geometry
| Field | Value |
|---|---|
| ID | DF-2725 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H |
| CWE | CWE-787 / CWE-131 |
| File | sys/kern/vfs_cluster.c |
| Lines | 954-959 (check), 1049-1066 (append), 1094-1100 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
cluster_rbuild() admits a new component whenever
(npages*PAGE_SIZE)+round_page(blksize) <= maxiosize and performs no
capacity check at all for the first component β but each component
actually contributes tbp->b_xio.xio_npages pages, which can exceed
round_page(blksize)/PAGE_SIZE by 1 whenever a component buffer
starts mid-page and crosses a page boundary. cluster_wbuild() gets
this right by summing actual page counts. With non-page-tiling
geometry the loop can accumulate more than maxiosize/PAGE_SIZE pages
into bp->b_xio.xio_pages β the embedded array is exactly
XIO_INTERNAL_PAGES=btoc(MAXPHYS) with kvasize=MAXPHYS β producing
(a) OOB writes of vm_page_t pointers past the embedded array in
struct buf and (b) pmap_qenter_noinval() installing PTEs past the
reserved MAXPHYS KVA window. The b_bufsize>b_kvasize panic does NOT
catch this (b_bufsize aggregates bytes, not pages). LATENT in-tree:
every in-tree caller produces page-tiling buffers (ffs/ext2 fragment
offsets are fsize-multiples and fsize|PAGE; msdosfs power-of-2
blsize at cluster-aligned offsets; tmpfs/fuse PAGE_SIZE; hammer 16K;
hammer2 uniform 64K; GETBLK_SZMATCH pins component size==blksize).
Reachable by any out-of-tree/third-party VFS (or future in-tree one)
whose block geometry is not page-tiling β then a plain sequential
read loop drives it.
Recommended fix
Mirror cluster_wbuild's exact accounting before appending each component's pages (test `bp->b_xio.xio_npages + tbp->b_xio.xio_npages
maxiosize / PAGE_SIZE` after the getblk, as :1609-1610 does) and cap the first component too.
Timeline
- 2026-08-30 Discovered during pass-2 audit of vfs_cluster.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2725 Β· 1 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | β | 770 B | view raw |
Fix verification
not_testableExact-npages admission fix (mirrors cluster_wbuild) compile-validated; latent - no in-tree trigger.
compile-validated in the fix-all kernel build (make nativekernel, 0 errors)
Confirmed kernel references
Detail
Evidence (decisive lines)
vfs_cluster.c:956-958 (round_page admit) vs :1049-1060 (actual npages append) vs :1609-1610 (wbuild correct)
PoC changes
n/a
Verified recommended fix
--- sys/kern/vfs_cluster.c 2026-06-29 12:51:19.000000000 +0000
Verdict
Line-accurate code confirmation; LATENT in-tree as the finding states. cluster_rbuild admits components using round_page(blksize) accounting (sys/kern/vfs_cluster.c:954-959) but appends tbp->b_xio.xio_npages actual pages (:1049-1066); a component crossing a page boundary mid-page costs one more page than the admission test counts, so a non-page-tiling filesystem geometry can push the pbufs embedded xio_pages[] window past maxiosize. cluster_wbuild uses exact npages accounting at :1606-1616. Every in-tree filesystem page-tiles its geometry and GETBLK_SZMATCH pins buffer sizes, so no in-tree workload can produce the precondition on this guest (verified: /boot UFS and hammer2 root both page-aligned). Reproduction requires an out-of-tree or future VFS with non-tiling block sizes; the fix (admit by actual npages after getblk, mirroring wbuild) is compile-validated.
No comments yet.