Skip to content

virtio: malformed virtqueue descriptor chain wedges the device worker in an unrecoverable busy-loop #4045

Description

@jstarks

Summary

A guest that makes a malformed split- or packed-virtqueue chain available drives its virtio device worker into a tight, non-terminating loop that pins the worker thread and makes the device un-cancellable — it can no longer observe a stop signal or be reset/torn down, which also blocks orderly shutdown of that VM.

Root cause

Descriptor-chain validation is correct — after queue_size descriptors the parser returns QueueError::TooLong (this also catches cycles), and out-of-bounds descriptor/indirect reads return QueueError::Memory. The defect is error recovery:

  • QueueCore::try_next_work() advances the available-ring index only on the Ok(Some(_)) arm; on any error the index is left unchanged, so the same malformed head is re-parsed on the next poll — vm/devices/virtio/virtio/src/queue.rs:283-292.
  • The queue stream surfaces the error immediately: in poll_next_buffer, self.try_next()? returns Poll::Ready(Err(_)) before reaching poll_kick, so the stream never waits for the next kick — vm/devices/virtio/virtio/src/common.rs:414-423.
  • The blk worker logs the error and re-enters its loop; with no in-flight I/O the inner poll_fn returns Ready every iteration, so the .await never yields Pending and the task never returns to the executor — vm/devices/virtio/virtio_blk/src/lib.rs:171-213.

Because run never returns from a single poll(), stop.until_stopped(...) can't observe the stop and worker.stop().await hangs during reset/teardown.

Reproduction

Found by the virtio-villain conformance/fault-injection suite. Triggering tests (virtio-blk, PCI): T0001, T0002, T0003, T0054, B0125, T0082, P0003 (packed), plus T0025/T0084 which exercise out-of-bounds available-ring indices. Each makes a bad head available and the device stops responding (guest observes WEDGED / timeout).

Impact

The offending guest wedges its own device: a pinned worker thread and a stuck, unresettable device that blocks orderly VM teardown. Confined to the guest that submitted the malformed chain.

Suggested fix

On a parse error, advance past (or otherwise retire) the offending head and complete it with an error / stop the queue, rather than re-polling the same head. Ensure the worker awaits the next kick (returns Pending) instead of spinning when the queue yields an error, and that a queue error still lets stop be observed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions