BLK_READ replies disclose stale shared pbuf-mem contents on EOF/error/short reads (cross-process, cross-device kernel-mediated info leak)
| Field | Value |
|---|---|
| ID | DF-2876 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| CWE | CWE-908 / CWE-200 |
| File | sys/kern/subr_diskiocom.c |
| Lines | 594-597, 651-655 (pool vm_pager.c:505) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
diskiodone sets data=bp->b_data, bytes=bp->b_count for BUF_CMD_READ before inspecting B_ERROR/b_resid and attaches that buffer to the reply unconditionally. A read that transfers nothing (vn disk at EOF: VOP_READ succeeds with untouched uio_resid β b_resid=b_bcount, no B_ERROR; or any erroring read where the driver never touched the buffer) returns error=0 with a 64KB aux containing the previous contents of the getpbuf_mem KVA arena β shared with every process's raw-device physio, disklabel probes and other dmsg I/Os. Each EOF/failed read harvests up to MAXPHYS of the most recent raw-I/O buffer of ANY process on the machine.
Proof of contest
VERIFIED on the stock guest (findings/poc/DF-2876/): vn0 backed by a 16MB urandom "secret" image, pool seeded via dd; peer issues BLK_READ at exactly offset==media_size β reply error=0 resid=65536 aux=65536 whose bytes are md5-IDENTICAL to the secret image block (deterministic across sessions); a second run leaked the boot disk's MBR boot code through a DIFFERENT disk's channel β cross-device disclosure. Fix (no aux on B_ERROR; only completed bytes on short reads) validated: aux=0 on all runs.
Recommended fix
Validated fix.diff in findings/poc/DF-2876/.
Timeline
- 2026-09-02 Discovered during pass-2 audit of subr_diskiocom.c (GLM 5.3); cross-process disclosure reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2876 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| dfpeer.c | β | 15.4 KB | view raw | |
| run_f3.sh | β | 1.7 KB | view raw | |
| run.log | β | 3.8 KB | view raw | |
| leak_sample.txt | β | 422 B | view raw | |
| f3_leak_1.bin | β | 64.0 KB | β download | |
| f3_secret.bin | β | 64.0 KB | β download | |
| fix.diff | β | 2.3 KB | view raw | |
| build.sh | β | 51 B | view raw | |
| run.sh | β | 143 B | view raw | |
| env.txt | β | 373 B | view raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| run.fix.log | β | 625 B | view raw | |
| verdict.json | β | 2.8 KB | view raw | |
| README.md | β | 1.9 KB | β raw |
DF-2876 β BLK_READ at/after EOF returns stale shared pbuf memory to the
dmsg peer (cross-process / cross-device kernel-mediated info leak)
What this pack contains
dfpeer.cβ dmsg wire peer (see DF-2875 pack).run_f3.shβ sets up a vn(4) disk containing 16MB of "secret" pattern, seeds the shared pbuf-mem pool by physio (dd if=/dev/vn0 of=/dev/null), then issues BLK_READ at EOF through the dmsg channel.leak_sample.txtβ the three leak runs + md5s; run 1 returned a full 64KB block identical to the secret image (md5 7081aa33...), run 2 returned the boot disk's MBR boot code (fc 31 c0 8e c0 8e d8 8e d0 bc 00 7c...) through a different disk's channel, run 3 returned stale dmsg write aux (0xAA fill).fix.diffβ diskiodone READ: on B_ERROR attach no data; on short reads attach only completed bytes.
Root cause
diskiodone (sys/kern/subr_diskiocom.c:594-597) sets data = bp->b_data;
bytes = bp->b_bcount for BUF_CMD_READ before looking at B_ERROR /
b_resid, and unconditionally attaches bytes of buffer content to the
reply (:651-655). A read that transfers nothing (EOF: vn's VOP_READ
returns success with untouched uio_resid β b_resid = b_bcount, no
B_ERROR) therefore ships the previous contents of the shared
getpbuf_mem KVA arena to the peer. That arena is shared with
kern_physio (every raw device I/O of every process), disklabel/GPT/MBR
probes, and other dmsg I/Os (sys/vm/vm_pager.c:505, sys/kern/kern_physio.c:43).
Build & run (root on the DF guest)
cc -O -Wall -o /tmp/dfpeer dfpeer.c sh run_f3.sh
Expected output (stock kernel)
eofread off=16777216 len=65536 -> reply cmd=... error=0 resid=65536 aux=65536
β an "error-free" reply whose 64KB aux equals the previous process's
physio buffer (the secret image block), verified by md5.
On the fix kernel the same command returns aux=0.
DF-2876 VERDICT
Status: reproduced (leak β deterministic cross-process /
cross-device kernel-mediated info leak to the dmsg peer)
Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (env.txt).
What was run
run_f3.sh:
1. root creates vn0 backed by a 16MB urandom image (the "secret"),
2. root seeds the shared pbuf-mem pool: dd if=/dev/vn0 of=/dev/null
(physio β getpbuf_mem β the KVA arena holds the secret,
sys/kern/kern_physio.c:43),
3. the dmsg peer issues BLK_READ at exactly EOF (offset = media size)
on the same disk.
Observed (stock kernel), 3 runs
- run 1: reply
error=0 resid=65536 aux=65536; the 64KB aux is byte-identical to the secret image block (md5 7081aa33β¦ both) β the read transferred nothing yet returned a previous process's physio buffer in full. - run 2: aux starts
fc 31 c0 8e c0 8e d8 8e d0 bc 00 7c β¦β the boot disk's MBR boot code, leaked through vn0's channel (cross-device disclosure; the arena is global). - run 3: aux = 0xAA fill β stale dmsg write aux from earlier testing.
Root cause: diskiodone sets data = bp->b_data; bytes = bp->b_bcount
before checking B_ERROR/b_resid (sys/kern/subr_diskiocom.c:594-597) and
attaches the buffer unconditionally (:651-655). An EOF read on a
vnode-backed vn disk returns success with untouched resid (vnstrategy
copies auio.uio_resid to b_resid, sys/dev/disk/vn/vn.c:337,385-389), so
the stale arena contents ship to the peer. The same happens for
B_ERROR completions (driver never touched the buffer).
Impact ceiling: any dmsg peer can repeatedly harvest up to MAXPHYS of the most recent raw-I/O buffer of ANY process on the machine (backups, fsck raw scans, other cluster nodes' disk traffic through diskiocom itself). Cluster-remote (AV:A) or local privileged.
Fix validation
fix.diff (no aux on B_ERROR; only completed bytes on short reads)
applied, kernel rebuilt, PoC re-run: eofread β¦ aux=0 β leak dead
(run.fix.log).
Fix verification
fixedFix kernel: all three EOF-read runs return aux=0 (resid still reported). Leak dead.
['run.fix.log']
Confirmed kernel references
Detail
Exploit chain
dmsg peer -> BLK_READ at/after media EOF (or any erroring read) -> reply aux = most recent raw-I/O buffer of ANY process on the machine (up to MAXPHYS per request, repeatable) -> harvest other tenants'/root's disk traffic (backups, fsck scans, other cluster nodes' data)
Evidence (decisive lines)
['run.log (error=0 resid=65536 aux=65536; LEAK_RUN_1 FULL_64K_BLOCK_MATCHES_SECRET; run2 leaked boot-sector bytes fc310c8e...)', 'leak_sample.txt', 'f3_leak_1.bin == f3_secret.bin (md5 7081aa33857826393038748400c1ee27)', 'run.fix.log (aux=0 x3 on fix kernel)']
PoC changes
No seed; wrote dfpeer.c eofread mode; deterministic EOF trigger discovered via vn(4) vnode strategy (success + untouched uio_resid at EOF)
Verified recommended fix
On B_ERROR attach no data; on short reads attach only completed bytes (b_bcount - b_resid)
Verdict
diskiodone attaches bp->b_data/b_bcount to the BLK reply before checking B_ERROR/b_resid (subr_diskiocom.c:594-597, :651-655), so a read that transfers nothing ships the stale contents of the SHARED getpbuf_mem KVA arena (vm_pager.c:505 - reused by every process's raw-device physio, kern_physio.c:43, and disk label probes). Verified: a BLK_READ at exact EOF on a vnode-backed vn disk (VOP_READ returns success with untouched resid) returned error=0 resid=65536 with a 64KB aux byte-identical (md5) to a previous root process's physio buffer of a 'secret' image; a second run leaked the BOOT DISK's MBR boot code through a different disk's channel (cross-device). Fixed kernel returns aux=0.
No comments yet.