Divide-by-zero panic via attacker-controlled blk_size=0 in virtio-blk config space
Summary
virtio_blk.c:702-709 if VIRTIO_BLK_F_BLK_SIZE negotiated sc->vtblk_sector_size = blkcfg->blk_size NO validation. 708 info.d_media_blksize = sector_size. 709 info.d_media_blocks = blkcfg->capacity*512 / info.d_media_blksize. blkcfg->blk_size is u32 from config space (hypervisor-controlled); 0 legal -> div by 0 #DE -> panic at attach. Capacity*512 also wraps u64 for capacity >= 2^55 (info only). Threat: malicious hypervisor/vhost-user/VFIO backend. Boot-time DoS. Fix: require blk_size >= DEV_BSIZE && powerof2.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1603 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df1603_poc.c | trigger-source | userspace harness reproducing the div-by-zero arithmetic of vtblk_alloc_disk lines 702-709 | 4.2 KB | view raw |
| df1603_fixed.c | fixed-logic-source | same harness WITH the fix.diff validation in place; no div-by-zero | 1.6 KB | view raw |
| build.sh | build-script | cc -O2 -o df1603_poc df1603_poc.c | 462 B | view raw |
| run.sh | run-script | ./df1603_poc | 229 B | view raw |
| build.log | build-log | trigger PoC build (rc=0) | 97 B | view raw |
| run.log | run-log | trigger PoC run showing SIGFPE on blk_size=0 | 328 B | view raw |
| fix_run.log | fix-run-log | fixed-logic harness: no div-by-zero, falls back to 512 | 258 B | view raw |
| fix_build.log | fix-build-log | patched-kernel build log (rc=0, 35424 lines) | 5.6 MB | β download |
| env.txt | environment | uname / kern.version / cc --version | 445 B | view raw |
| fix.diff | suggested-fix | git-apply-able fix to virtio_blk.c: validate blk_size >= DEV_BSIZE && powerof2 | 1008 B | view raw |
| VERDICT.md | verdict | REPRODUCED at harness level; live trigger requires malicious hypervisor | 7.3 KB | β raw |
| README.md | readme | human-facing reproduction guide | 2.5 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1603 β virtio_blk divide-by-zero PoC
TL;DR
sys/dev/virtual/virtio/block/virtio_blk.c:702-709 divides
blkcfg->capacity * 512 / info.d_media_blksize where
info.d_media_blksize = sc->vtblk_sector_size = blkcfg->blk_size (set at
line 703 without any validation when VIRTIO_BLK_F_BLK_SIZE is negotiated).
blkcfg->blk_size is a u32 read verbatim from the virtio PCI config space,
which is host/hypervisor-controlled. A malicious backend that negotiates the
feature AND reports blk_size = 0 triggers a #DE trap at attach time, which
the kernel converts to a panic. Effect: boot-time DoS of the guest by a
malicious / compromised hypervisor, vhost-user backend, or hostile VFIO
device.
The bug is not an in-guest unprivileged attack surface β it requires the host side to shape virtio config space. On this DragonFly audit guest the virtio-blk device is provided by KVM with a sane sector size and there is no in-guest syscall that can rewrite PCI config space.
How to reproduce
./build.sh
./run.sh
Expected output (the trigger case):
[control] capacity=0x100000 blk_size=512 -> 1048576 blocks x 512 bytes
[OK] blk_size=0 raised SIGFPE (analogue of #DE trap);
kernel path: vtblk_attach -> vtblk_alloc_disk line 709
result on guest: kernel panic at attach time (boot DoS).
The harness exit code is 0 when the SIGFPE (analogue of the kernel's #DE
trap) fires on blk_size=0.
Why a harness instead of an in-guest trigger
The kernel path runs at device-attach time (vtblk_attach β
vtblk_alloc_disk) and consumes data supplied by the host side via
virtio_read_device_config(). From inside a running guest there is no way
for an unprivileged user β or even root β to rewrite the virtio-blk config
space and force blk_size = 0. To fire the kernel path you would need to be
the hypervisor (or a vhost-user backend, or a hostile VFIO device). The
harness reproduces the EXACT arithmetic of lines 702-709 so we can confirm
the primitive without that capability.
Fixed-logic variant
df1603_fixed.c incorporates the same validation the fix.diff introduces
(blk_size >= DEV_BSIZE && powerof2(blk_size)). Build + run:
cc -O2 -o df1603_fixed df1603_fixed.c
./df1603_fixed
Expected output:
[fixed] blk_size=0 -> sector_size=512 (fell back to 512), blocks=1048576 - NO div-by-zero [OK] fix confirmed: blk_size=0 no longer reaches the division; kernel would not panic.
Fix
See fix.diff (validated: applies cleanly, compiles with rc=0, see
fix_build.log). The patched kernel boots cleanly.
DF-1603 β virtio_blk divide-by-zero via attacker-controlled blk_size=0
Verdict
REPRODUCED at the harness level (the kernel code path itself is not
triggerable from inside the guest β see Reachability below). The bug is a
real divide-by-zero, confirmed by tracing the cited source and by a userspace
harness that mirrors the exact arithmetic of vtblk_alloc_disk() lines 702-709
of sys/dev/virtual/virtio/block/virtio_blk.c.
Impact: panic (boot-time DoS of a guest by a malicious hypervisor /
vhost-user / VFIO backend). CVSS:3.1 AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H
matches the finding β a host-side attacker with the ability to shape virtio
config space can panic the guest at first attach.
Not memory corruption; the Phase 6 escalation chain is not applicable (a #DE trap panics the kernel directly; there is no write primitive to convert).
Mechanism (trigger β primitive β effect), cited line-by-line
In sys/dev/virtual/virtio/block/virtio_blk.c:
vtblk_attach()(line 242) reads the device config space into a localstruct virtio_blk_config blkcfgat lines 263-264 viavirtio_read_device_config(). Every field ofblkcfgis host-controlled.vtblk_attach()then callsvtblk_alloc_disk(sc, &blkcfg)at line 371.vtblk_alloc_disk()at lines 702-705:
c
if (virtio_with_feature(sc->vtblk_dev, VIRTIO_BLK_F_BLK_SIZE))
sc->vtblk_sector_size = blkcfg->blk_size; /* 703 - NO validation */
else
sc->vtblk_sector_size = 512; /* 705 */
blkcfg->blk_size is a uint32_t (sys/dev/virtual/virtio/block/virtio_blk.h:64)
taken verbatim from PCI config space. The virtio-blk specification permits a
backend to negotiate VIRTIO_BLK_F_BLK_SIZE and report any u32 value,
including 0. There is no validation that the value is non-zero, that it is
a multiple of DEV_BSIZE, or that it is a power of two.
- Lines 708-709 then divide by it unconditionally:
c
info.d_media_blksize = sc->vtblk_sector_size; /* 708 */
info.d_media_blocks = blkcfg->capacity * 512 / info.d_media_blksize; /* 709 */
If blkcfg->blk_size was 0, the division on line 709 takes a #DE (divide
error) trap. The kernel has no handler for #DE in this path and converts it
to a panic. Because vtblk_alloc_disk() runs unconditionally from
vtblk_attach(), the panic happens during device probe on first boot, before
any userspace exists.
Reachability β why the kernel path is not exercised in this guest
The DragonFly QEMU audit guest boots with vtblk0 provided by KVM. KVM/QEMU
report a sane 512-byte sector size, and the guest has no syscall surface that
would let an in-guest attacker rewrite PCI config space. Triggering the bug
requires one of:
- a malicious / compromised hypervisor that advertises
VIRTIO_BLK_F_BLK_SIZEand reportsblk_size = 0; - a malicious vhost-user backend (e.g. a compromised
vhost-user-blkprocess) responding to config-space reads; - a hostile VFIO device returning
blk_size = 0from its config DMA.
None of these is exercisable from unprivileged in-guest userspace. This is a host-side DoS of a guest, not a guest-internal privesc. Per the Phase 6 "valid hard blockers" list this is the "primitive is reachable only from a context we don't control on this guest" case β we prove the primitive at the harness level (analogous to DF-0594/0616/0281) and document the live trigger conditions.
Harness proof (the reproduction)
df1603_poc.c mirrors vtblk_alloc_disk() lines 702-709 with bit-exact
arithmetic. The "control" case (blk_size=512) divides cleanly. The "trigger"
case (feature_negotiated=1, blk_size=0) divides by zero and raises SIGFPE
β the userland analogue of the kernel's #DE trap. Build + run:
$ cc -O2 -o df1603_poc df1603_poc.c
$ ./df1603_poc
[control] capacity=0x100000 blk_size=512 -> 1048576 blocks x 512 bytes
[OK] blk_size=0 raised SIGFPE (analogue of #DE trap);
kernel path: vtblk_attach -> vtblk_alloc_disk line 709
result on guest: kernel panic at attach time (boot DoS).
Fix
fix.diff adds minimal validation: only honour blk_size when it is
>= DEV_BSIZE and a power of two; otherwise fall back to the spec's implicit
512-byte default. The full git-apply-able diff is in fix.diff. Highlights:
if (virtio_with_feature(sc->vtblk_dev, VIRTIO_BLK_F_BLK_SIZE) &&
blkcfg->blk_size >= DEV_BSIZE &&
powerof2(blkcfg->blk_size))
sc->vtblk_sector_size = blkcfg->blk_size;
else
sc->vtblk_sector_size = 512;
powerof2 and nitems come from <sys/param.h> (already included).
DEV_BSIZE comes from <machine/param.h> (also already pulled in via
<sys/param.h>). No new includes are required.
The fix matches (and sharpens) the finding proposal: "Fix: require blk_size >= DEV_BSIZE && powerof2."
Fix validation
We validated fix.diff per Phase 8:
git apply --checksucceeds against the hostsys/tree.patch -p1 --forward < fix.diffsucceeded in-guest on/usr/src(hunk #1 applied at line 699).make -j6 nativekernel KERNCONF=X86_64_GENERICfrom/usr/srcproducedkernel.strippedandkernel.debugwith rc=0 and no errors β full build log is infix_build.log(35,424 lines).- The patched kernel was installed to
/boot/kernel/kerneland the guest rebooted intokern.version = "DragonFly 6.5-DEVELOPMENT #2: Sat Jul 18 11:46:12 UTC 2026"(sha256394311892793c84e1f6fb6ed1c14c53271c617f3121fee8c8a647ebc7a51e32a). - A "fixed-logic" harness (
df1603_fixed.c) reproduces the same arithmetic WITH the fix's validation in place; withblk_size=0it falls back to 512 and produces no division. Run output is infix_run.log.
fix_status: not_testable for the kernel-level PoC (the live kernel path
cannot be triggered on this guest without a malicious hypervisor). The fix
itself is compile-validated and the logic is shown correct by the
fixed-logic harness.
Files in this evidence pack
| File | Type | Description |
|---|---|---|
df1603_poc.c |
trigger-source | userspace harness reproducing the div-by-zero arithmetic |
df1603_fixed.c |
fixed-logic-source | same harness WITH the fix's validation in place |
build.sh |
build-script | cc -O2 -o df1603_poc df1603_poc.c |
run.sh |
run-script | ./df1603_poc |
build.log |
build-log | full build output of the trigger PoC (exits 0) |
run.log |
run-log | full run output of the trigger PoC (SIGFPE observed) |
fix_run.log |
fix-run-log | fixed-logic harness output (no div-by-zero) |
fix_build.log |
fix-build-log | full patched-kernel build (rc=0, 35,424 lines) |
env.txt |
environment | uname / kern.version / cc --version |
fix.diff |
suggested-fix | git-apply-able fix (matches finding proposal) |
VERDICT.md |
verdict | this file |
manifest.json |
manifest | machine-readable catalog |
Fix verification
not_testablecompile+harness validated
kernel build rc=0 + harness before/after
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). vtblk_alloc_disk blk_size=0 -> div-by-zero at attach. Malicious hypervisor host-side DoS. Harness SIGFPE.
No comments yet.