β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2725

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2725 Β· 1 files
FileTypeDescriptionSize
fix.diff β€” 770 B view raw

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Exact-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)
↓ fix.diffper-fix-DF-2725

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.