mbrinit/mbr_extended trust device-reported d_media_blksize β up to 2 GiB synchronous read into a MAXPHYS (128 KB) pbuf: assertion-cast bypass (all kernels) or INVARIANTS panic; proven OOB write with attacker-controlled content/length
| Field | Value |
|---|---|
| ID | DF-2902 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| CWE | CWE-130 / CWE-787 / CWE-190 |
| File | sys/kern/subr_diskmbr.c |
| Lines | 127-135 (esp. :128, :132), 434-442; feed: scsi_da.c:2289 |
| Area | kern/disk |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
mbrinit() issues a one-block synchronous read sized exactly
info->d_media_blksize into a getpbuf_mem() pbuf whose buffer is only
MAXPHYS = 128 KB. The sole guard, KKASSERT((int)info->d_media_blksize
<= bp->b_bufsize) at subr_diskmbr.c:128 (and :435 for EBR reads), is
doubly broken: KKASSERT compiles out on non-INVARIANTS production
kernels, and the (int) cast turns any block size β₯ 2^31 into a negative
int that PASSES the assertion everywhere. d_media_blksize is
device-controlled end to end: a malicious USB/SCSI storage device
answers READ CAPACITY with length=0x80000000, scsi_da.c stores it into
params.secsize with no validation, :1863 propagates it to
d_media_blksize, and _setdiskinfo even computes the non-zero
d_media_size that defeats mbrinit's media-size guard. bp->b_bcount =
0x80000000 then flows through dscheck (whose int-wrap arithmetic is
self-consistent for this exact power of two) into scsi_read_write β
with scsi_minphys #if 0'd β i.e. a device-directed transfer of up to
2 GiB into a 128 KB kernel buffer whose content the device fully
controls. Entry: device attach-time probe (runs as root in
disk_msg_core), no user interaction; non-hardware entry requires root
(vnconfig/KLD), raw disk opens require SYSCAP_RESTRICTEDROOT β no
unprivileged-local entry.
Proof of contest
VERIFIED (findings/poc/DF-2902/): (a) KLD harness on /dev/vn0 with
d_media_blksize=0x40000 β exact-line assertion panic at
subr_diskmbr.c:128; (b) d_media_blksize=0x80000000 β assertion
bypassed via the cast, 2 GiB read issued, transport memmove overruns
the 128 KB pbuf β kernel-mode write page fault (trap 0xc, rip =
memmove+0x10a) β overflow content is the attacker's media bytes.
uid=0 route: hard-blocked β the malicious-device vector is unavailable
in this guest (QEMU usb-storage cannot lie about READ CAPACITY); entry
is device-attach or root-only. Fix validated by in-guest nativekernel
rebuild: both legs β EIO, no panic.
Recommended fix
Bound the block size before use (< DEV_BSIZE || > MAXPHYS β EIO) in
both mbrinit and the mbr_extended read, plus clamp at the source in
scsi_da.c dasetgeom. Validated fix.diff in findings/poc/DF-2902/.
Timeline
- 2026-09-02 Discovered during pass-2 audit of subr_diskmbr.c (GLM 5.3); reproduced both legs + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2902 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| mbrprobe.c | β | 2.2 KB | view raw | |
| Makefile | β | 89 B | β download | |
| build.sh | β | 544 B | view raw | |
| run.sh | β | 642 B | view raw | |
| run.log | β | 2.2 KB | view raw | |
| run_fix.log | β | 694 B | view raw | |
| panic_runA.txt | β | 756 B | view raw | |
| panic_runB.txt | β | 850 B | view raw | |
| env.txt | β | 640 B | view raw | |
| fix.diff | β | 1.2 KB | view raw |
DF-2902 β unvalidated d_media_blksize in mbrinit β oversized kernel read into 128 KB pbuf
What
mbrinit() (sys/kern/subr_diskmbr.c:127-132) issues a synchronous read of
exactly info->d_media_blksize bytes into a getpbuf_mem() pbuf whose
buffer is only MAXPHYS (128 KB on x86_64) bytes:
bp = getpbuf_mem(NULL);
KKASSERT((int)info->d_media_blksize <= bp->b_bufsize); /* :128 β broken guard */
bp->b_bio1.bio_offset = (off_t)mbr_offset * info->d_media_blksize;
bp->b_bcount = info->d_media_blksize; /* :132 β sink */
The guard is broken twice:
1. KKASSERT is compiled out entirely on non-INVARIANTS (production) kernels.
2. The (int) cast makes any block size β₯ 2^31 (e.g. 0x80000000 β INT_MIN)
pass the assertion even on INVARIANTS kernels.
d_media_blksize is attacker-controllable by any malicious storage device:
scsi_da.c:dasetgeom() copies the READ CAPACITY length field verbatim into
softc->params.secsize (sys/bus/cam/scsi/scsi_da.c:2289) with no validation,
so a USB mass-storage gadget answering READ CAPACITY with length=0x80000000
delivers it straight into mbrinit at plug-in (devd/CAM auto-probe runs as
root). The same value flows to mbr_extended() (subr_diskmbr.c:435-439).
Observed (stock INVARIANTS guest, DragonFly 6.5-DEVELOPMENT)
- blksize = 0x40000 (256 KB):
panic: assertion "(int)info->d_media_blksize <= bp->b_bufsize" failed in mbrinit at subr_diskmbr.c:128(see panic_runA.txt). - blksize = 0x80000000 (2 GiB): assertion BYPASSED via the cast; the read is
issued with
b_bcount = 0x80000000into the 128 KB pbuf; the transfer memmove runs off the end of the mapped buffer β kernel-mode page fault:trap 0xc, rip = memmove+0x10a(see panic_runB.txt). The overflow content is the attacker's data (the bytes the device/backing file supplies).
Threat
Crafted MBR media is NOT required β only a device that lies about its block
size (USB mass-storage, malicious virtual disk, misbehaving HBA). On the
real CAM path the same b_bcount becomes the SCSI dxfer_len
(scsi_da.c:1492-1506, scsi_minphys is #if 0'd), i.e. a device-directed
DMA of up to 2 GiB into a 128 KB kernel buffer β attacker-chosen content and
length: code-execution-grade primitive. Reach: plug-in auto-probe (no
privilege needed by the attacker beyond physical access).
Reproduce (guest, root)
dd if=/dev/zero of=/root/med.img bs=1m count=256 vnconfig -c -S 512 vn0 /root/med.img # clean zero media cd /root/mbrprobe && make # KLD harness kenv mbrprobe.blksize=262144 && kldload ./a/mbrprobe_a.ko # panic at :128 # reset, reattach vn, then: kenv mbrprobe.blksize=2147483648 && kldload ./b/mbrprobe_b.ko # OOB memmove panic
Success criterion: panics above on the stock kernel; after fix.diff both
runs print mbrprobe: mbrinit returned 5 (EIO) and the guest stays up.
DF-2902 VERDICT
Status: reproduced (both legs) β impact: kernel memory corruption (proven OOB write w/ attacker-controlled content and length; demonstrated as panic), gated on a malicious-storage-device / root trigger.
Chain (all lines verified in-guest)
- Attacker device answers SCSI READ CAPACITY(10/16) with
length= chosen value.scsi_da.c:1831/1850block_size = scsi_4btoul(...);dasetgeom()scsi_da.c:2289dp->secsize = block_len;β no validation anywhere. scsi_da.c:1863info.d_media_blksize = softc->params.secsizeβdisk_setdiskinfo()(subr_disk.c:853) β_setdiskinfocomputesd_media_size = blocks*blksize(nonzero, so mbrinit's media-size guard passes) β DISK_DISK_PROBE.disk_msg_core(holdingds_token, subr_disk.c:512) βdisk_probe(subr_disk.c:371) βmbrinit().subr_diskmbr.c:118blksize & DEV_BMASKβ passes for any 512-multiple.subr_diskmbr.c:128KKASSERT((int)blksize <= bp->b_bufsize): * 0x20000 < blksize < 0x80000000 (512-multiple) β fires on INVARIANTS kernels (panic reproduced, panic_runA.txt: blksize=0x40000); * blksize β₯ 0x80000000 β(int)cast makes it negative β assertion passes on every kernel.subr_diskmbr.c:132bp->b_bcount = blksizeon a pbuf whose b_data is a MAXPHYS (128 KB, sys/cpu/x86_64/include/param.h:122) KVA window (vm_pager.c:263/391).- The read walks diskstrategyβdscheck (all boundary checks pass: the
arithmetic is self-consistent in wrap-around int space β verified
subr_diskslice.c:124-282) β raw dev strategy. On CAM:
scsi_da.c:1492-1506scsi_read_write(..., bp->b_data, bp->b_bcount)withscsi_minphys#if 0'd (scsi_da.c:706-711) β dxfer_len = up to 2 GiB DMA into the 128 KB pbuf. In the harness (vn transport) the equivalent is the uiomove copy (vn.c:323-333): kernel-mode page fault in memmove reproduced (panic_runB.txt:trap 000000000000000c, rip = memmove+0x10a).
Why not uid0
The entry vector is device-attach-time probe: it requires a malicious
storage device (hardware; no USB gadget emulation is available on this QEMU
guest to fake READ CAPACITY block length) or root (vnconfig/KLD). There
is no unprivileged-local entry to this path (opening disk devices requires
SYSCAP_RESTRICTEDROOT, subr_disk.c:1072). The corruption primitive itself
(attacker bytes, attacker length, deterministic forward overflow from a
pager_map pbuf slot) is fully demonstrated; weaponizing to uid0 needs the
hardware vector and target-grooming work that the guest cannot host.
Recorded as memcorrupt/High with panic-level demonstrated impact.
Fix validation (this run)
- fix.diff adds
if (info->d_media_blksize < DEV_BSIZE || info->d_media_blksize > MAXPHYS) return (EIO);at subr_diskmbr.c:121 (plus the DF-2903 hunk), built withmake nativekernelin-guest and booted. - Baseline (stock kernel): panic at :128 (blksize 0x40000); OOB memmove
page-fault panic (blksize 0x80000000). Patched kernel: both runs print
mbrprobe: mbrinit returned 5(EIO) and the guest stays up β bad behavior GONE. See run_fix_*.log, fix_build.log.
Fix verification
fixedPatched kernel rejects hostile block sizes with EIO before issuing any I/O; both baseline panics gone (run_fix.log).
['run_fix.log', 'env.txt (BUILD_RC=0 INSTALL_RC=0, kernel #1 uname)']
Confirmed kernel references
Detail
Exploit chain
malicious USB/SCSI device -> READ CAPACITY lies block_len=0x80000000 -> dasetgeom (no validation) -> disk_setdiskinfo -> disk_msg_core probe -> mbrinit: (int)cast bypasses KKASSERT -> bp->b_bcount=0x80000000 on 128KB pbuf -> dscheck passes (int-wrap arithmetic is self-consistent) -> scsi_read_write(dxfer_len=2GiB, data_ptr=pbuf) -> device DMA / transport copy writes attacker bytes over kernel memory from the pbuf KVA slot forward. Harness equivalent (vn transport): memmove page fault reproduced.
Evidence (decisive lines)
['panic_runA.txt: exact-line KKASSERT panic at subr_diskmbr.c:128 (blksize 0x40000)', "panic_runB.txt / run.log LEG B: trap 000000000000000c rip=memmove+0x10a after 'mbrprobe: dev=vn0 blksize=0x80000000' β the assertion was bypassed and the oversized copy faulted", "run_fix.log: patched kernel β both legs 'mbrinit returned 5' (EIO), guest survives", 'VERDICT.md: full path trace with citations']
PoC changes
Seed sketch did not exist; harness authored from scratch: KLD (DEV_MODULE) fetches /dev/vn0 via devfs_find_device_by_name, builds a hostile struct disk_info from the kenv tunable mbrprobe.blksize, allocates a slice struct and invokes mbrinit() directly β replicating the probe call with attacker-chosen block size; a/b module-name variants allow repeat runs. Initial 0x20000 run was benign (MAXPHYS is 128KB on x86_64, not 64KB) β corrected to 0x40000/0x80000000.
Verified recommended fix
Validate d_media_blksize in mbrinit/mbr_extended: reject < DEV_BSIZE or > MAXPHYS (and clamp at the dasetgeom source in scsi_da.c)
Verdict
mbrinit() trusts the device-reported d_media_blksize: subr_diskmbr.c:132 programs bp->b_bcount = blksize on a getpbuf_mem() pbuf whose buffer is MAXPHYS (128KB) bytes. The only guard (:128) is a KKASSERT that (a) compiles out on production kernels and (b) is bypassed for blksize >= 2^31 by the (int) cast. blksize is fully attacker-controlled by a malicious storage device via READ CAPACITY (scsi_da.c:dasetgeom stores it unvalidated). Reproduced both legs on the stock INVARIANTS guest: blksize 0x40000 -> exact-line panic at subr_diskmbr.c:128; blksize 0x80000000 -> assertion bypassed, 2GiB copy into the 128KB pbuf, kernel-mode write page fault in memmove (trap 0xc) β a proven OOB write whose content is the attacker's media bytes. Demonstrated impact is panic; on the real CAM path the same b_bcount becomes the SCSI dxfer_len (scsi_minphys is #if 0'd), i.e. a device-directed DMA of up to 2GiB into a 128KB kernel buffer β code-execution-grade primitive gated on malicious hardware / root (no unprivileged-local entry exists: raw disk opens require SYSCAP_RESTRICTEDROOT). fix.diff (blksize bound check) rebuilt in-guest: both legs then return EIO with no crash.
No comments yet.