# 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

```sh
./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:

```sh
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.
