Skip to content

Check whole virtqueue rings in guest memory - #533

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

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

Conversation

@Lencerf

@Lencerf Lencerf commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

SplitQueue::new() and PackedQueue::new() check only the first entry of each
ring in guest memory, but the queues access entries up to the queue size
through the same pointers. A driver that places a ring at the end of guest
memory, or across two memory regions, makes the device worker read and write
host memory beyond the guest memory mapping.

  • Add Ram::get_array_ptr(), which checks that a whole array is backed by one
    contiguous host mapping, and fix the alignment check of Ram::get_ptr(),
    which never failed.
  • Check whole descriptor tables, available rings and used rings of split
    virtqueues.
  • Check whole packed rings, reject packed queue sizes above 2^15, and stop at a
    descriptor chain as long as the ring, which also keeps the ring offset of the
    next chain within the ring.

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.

Ram::get_ptr() only checks that one value of the pointee type is backed
by host memory, while callers like virtqueues use the pointer for an
array of values. Its alignment check also tests the byte pointer before
the cast, so it never fails.

Add Ram::get_array_ptr(), which checks that all the values are in one
contiguous host mapping, and that the pointer is aligned for the
pointee type. Ram::get_ptr() becomes the case of a single value, which
fixes its alignment check.

Signed-off-by: Changyuan Lyu <changyuanl@google.com>
SplitQueue::new() checks only the first entry of the descriptor table,
the available ring and the used ring, but the queue accesses entries up
to the queue size through the same pointers. A driver that places a ring
at the end of guest memory, or across two memory regions, makes the
worker read and write host memory beyond the guest memory mapping.

Check that each ring is backed by one contiguous host mapping in full.

Fixes: fc059ad ("perf(virtio): lock memory layout while running")
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
Like SplitQueue::new() did, PackedQueue::new() checks only the first
descriptor of the ring, but the queue accesses descriptors up to the
queue size. A driver that places the ring at the end of guest memory,
or across two memory regions, makes the worker read and write host
memory beyond the guest memory mapping.

Check that the whole ring is backed by one contiguous host mapping, and
reject queue sizes above 2^15, the maximum for packed virtqueues, which
is also all that the 15-bit ring offset in WrappedIndex can address.

Also stop at a descriptor chain as long as the ring, since a chain takes
consecutive descriptors. Otherwise, a driver that sets VIRTQ_DESC_F_NEXT
in every descriptor makes the worker loop forever, and a driver that
changes the flags while the worker walks the chain can end it after more
descriptors than the ring has, which moves the offset of the next chain
beyond the ring.

Fixes: f97eb72 ("feat(virtio): support packed queue")
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
@Lencerf
Lencerf force-pushed the fix/virtqueue-ring-bounds branch from f131e56 to 474cc60 Compare November 9, 2026 21:01
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 42698485359

Coverage increased (+0.09%) to 39.495%

Details

  • Coverage increased (+0.09%) from the base build.
  • Patch coverage: 17 of 17 lines across 3 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: 16240
Covered Lines: 6217
Line Coverage: 38.28%
Relevant Branches: 1676
Covered Branches: 859
Branch Coverage: 51.25%
Branches in Coverage %: Yes
Coverage Strength: 896.16 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 e0b77c7 Nov 9, 2026
20 checks passed
@Lencerf
Lencerf deleted the fix/virtqueue-ring-bounds branch November 9, 2026 22:00
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