Skip to content

Limit virtqueue descriptor chains to the queue size - #536

Merged
Lencerf merged 3 commits into
google:mainfrom
Lencerf:fix/virtqueue-chain-bounds
Nov 9, 2026
Merged

Lencerf merged 3 commits into
google:mainfrom
Lencerf:fix/virtqueue-chain-bounds

Conversation

@Lencerf

@Lencerf Lencerf commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

SplitQueue::get_avail() walks a descriptor chain without a limit and asserts
that an indirect table has no indirect descriptors. A guest can panic the device
worker with a nested indirect descriptor, or link a chain back to itself to keep
the worker spinning and growing the buffer lists until the host runs out of
memory. PackedQueue::get_avail() takes every descriptor of an indirect table,
whose length comes from the guest, and accepts an indirect descriptor at each
descriptor of a list.

  • Reject an indirect descriptor in an indirect table or with
    VIRTQ_DESC_F_NEXT set in split queues, instead of panicking.
  • Fail a split descriptor chain longer than the queue size, counting the
    descriptors in an indirect table (VirtIO spec 1.4, Sec 2.7.5.3.1), and read
    only the descriptors within an indirect table.
  • Reject an indirect descriptor in a list of a packed queue (VirtIO spec 1.4,
    Sec 2.8.19) and limit an indirect table to the queue size.

alioth does not offer VIRTIO_F_INDIRECT_DESC, and a conforming driver never
creates a chain longer than the queue size, so these checks do not affect
conforming guests.

Every commit passes cargo +nightly fmt --check, cargo clippy --all-targets -D warnings on x86_64-unknown-linux-gnu/musl, aarch64-unknown-linux-gnu and
aarch64-apple-darwin, and cargo test --workspace locally.

An indirect descriptor can only be the last descriptor of a chain in
the descriptor table: it cannot have VIRTQ_DESC_F_NEXT set or be in an
indirect table (VirtIO spec 1.4, Sec 2.7.5.3.1).

SplitQueue::get_avail() asserted the latter, so a guest could panic the
device worker with an indirect descriptor in an indirect table. Return
an error in both cases instead.

Fixes: bd56123 ("feat(virtio): implement split queue operations")
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
A driver cannot create a descriptor chain longer than the queue size,
counting the descriptors in an indirect table (VirtIO spec 1.4, Sec
2.7.5.3.1). However, SplitQueue::get_avail() follows `next` without a
limit, so with a loop in the descriptor table or in an indirect table,
a guest could keep the device worker spinning and growing the buffer
lists until the host runs out of memory.

Fail a chain once it has more descriptors than the queue size. Also
take only the descriptors within the length of an indirect table.

Fixes: bd56123 ("feat(virtio): implement split queue operations")
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
PackedQueue::get_avail() takes every descriptor in an indirect table,
whose length comes from the guest, and accepts an indirect descriptor
at each descriptor of a list. With a list of indirect descriptors
referring to a large table, a guest could make the device worker spend
CPU time and host memory far beyond the size of the guest memory.

An indirect descriptor cannot be in a list (VirtIO spec 1.4, Sec
2.8.19), so reject it there. The same section lets the device decide
the maximum length of a chain; like a split queue, limit an indirect
table to the queue size.

Fixes: f97eb72 ("feat(virtio): support packed queue")
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 42721820371

Coverage increased (+0.2%) to 39.717%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 30 of 30 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 16256
Covered Lines: 6250
Line Coverage: 38.45%
Relevant Branches: 1686
Covered Branches: 876
Branch Coverage: 51.96%
Branches in Coverage %: Yes
Coverage Strength: 903.35 hits per line

💛 - Coveralls

@Lencerf
Lencerf added this pull request to the merge queue Nov 9, 2026
Merged via the queue into google:main with commit 421f2d2 Nov 9, 2026
20 checks passed
@Lencerf
Lencerf deleted the fix/virtqueue-chain-bounds branch November 9, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants