Problem
VirtioBlkDevice::process_queue (src/devices/virtio_blk.rs) services requests with while self.avail_idx != avail_idx, where avail_idx is the 16-bit index the guest wrote to the available ring. A queue holds at most queue_size buffers (128 at most), so a larger gap describes buffers the guest cannot have posted, but nothing checks it: a guest that writes avail.idx = device.avail_idx - 1 makes one kick service 65535 requests. Each request may name up to MAX_REQUEST_BYTES (32 MiB) across 30 data descriptors, so one kick can schedule on the order of 2 TiB of disk reads and guest-memory writes on the vCPU thread. The MAX_REQUEST_BYTES doc comment says the ceiling "bounds what a guest can schedule in one kick"; it bounds one request, not the kick.
virtio-net has this guard (batched >= queue_size in process_tx_queue, from #172); virtio-blk does not. virtio-blk carries the OCI base rootfs, so the path runs on every Linux/KVM run with an image.
Fix
Mirror virtio-net: count requests per kick and stop at queue_size, deferring the rest to the next kick, which picks up anything genuinely outstanding. Correct the MAX_REQUEST_BYTES doc comment. Add a unit test next to a_data_descriptor_naming_unmapped_memory_is_refused.
The virtio_blk fuzz target from #180 already carries fuzz/corpus/virtio_blk/avail_idx_far_ahead, an available index 4096 ahead of the device's. It is the regression seed for this bound once the fix lands. Under the harness the unbounded loop shows as a slow unit rather than a crash, so the unit test is the oracle, not the replay gate.
Context
Surfaced by adversarial review of the #180 harnesses. Related: #170, which tracks the guard matrix across the descriptor walkers.
Problem
VirtioBlkDevice::process_queue(src/devices/virtio_blk.rs) services requests withwhile self.avail_idx != avail_idx, whereavail_idxis the 16-bit index the guest wrote to the available ring. A queue holds at mostqueue_sizebuffers (128 at most), so a larger gap describes buffers the guest cannot have posted, but nothing checks it: a guest that writesavail.idx = device.avail_idx - 1makes one kick service 65535 requests. Each request may name up toMAX_REQUEST_BYTES(32 MiB) across 30 data descriptors, so one kick can schedule on the order of 2 TiB of disk reads and guest-memory writes on the vCPU thread. TheMAX_REQUEST_BYTESdoc comment says the ceiling "bounds what a guest can schedule in one kick"; it bounds one request, not the kick.virtio-net has this guard (
batched >= queue_sizeinprocess_tx_queue, from #172); virtio-blk does not. virtio-blk carries the OCI base rootfs, so the path runs on every Linux/KVM run with an image.Fix
Mirror virtio-net: count requests per kick and stop at
queue_size, deferring the rest to the next kick, which picks up anything genuinely outstanding. Correct theMAX_REQUEST_BYTESdoc comment. Add a unit test next toa_data_descriptor_naming_unmapped_memory_is_refused.The
virtio_blkfuzz target from #180 already carriesfuzz/corpus/virtio_blk/avail_idx_far_ahead, an available index 4096 ahead of the device's. It is the regression seed for this bound once the fix lands. Under the harness the unbounded loop shows as a slow unit rather than a crash, so the unit test is the oracle, not the replay gate.Context
Surfaced by adversarial review of the #180 harnesses. Related: #170, which tracks the guard matrix across the descriptor walkers.