From 544fa4aa764abae9d7cbe53c69598521570360a8 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 13 May 2026 22:11:31 +0000 Subject: [PATCH] virtio-devices: block: reject duplicate in-flight head_index A malicious or buggy guest can violate virtio by making the same descriptor head available twice before the first chain has been placed on the used ring. The submit path pushed both chains onto the VecDeque-backed inflight_requests keyed by head_index, and on completion find_inflight_request() returned the first linear match. That Request's complete_async() freed its bounce buffer while the other chain's io_uring op was still targeting it, producing a use-after-free the kernel could then scribble into. Signed-off-by: Dylan Reid Signed-off-by: Bo Chen --- virtio-devices/src/block.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 23a0dbb3f..e6e8596e0 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -85,6 +85,8 @@ pub enum Error { QueueAddUsed(#[source] virtio_queue::Error), #[error("Failed creating an iterator over the queue")] QueueIterator(#[source] virtio_queue::Error), + #[error("Duplicated head index in the queue")] + QueueDuplicatedHeadIndex, #[error("Failed to update request status")] RequestStatus(#[source] GuestMemoryError), #[error("Failed to enable notification")] @@ -200,6 +202,17 @@ impl BlockEpollHandler { Ok(()) } + // A spec-compliant driver never reuses a virtqueue head_index while the + // corresponding chain is still available (virtio 1.x ยง2.7.13.4). + // Double check the guest driver is behaving. + fn is_head_in_flight( + inflight: &VecDeque<(u16, Request)>, + batch: &[(u16, Request)], + head: u16, + ) -> bool { + batch.iter().any(|(h, _)| *h == head) || inflight.iter().any(|(h, _)| *h == head) + } + fn process_queue_submit(&mut self) -> Result<()> { let queue = &mut self.queue; let queue_size = queue.size(); @@ -222,6 +235,13 @@ impl BlockEpollHandler { Some(c) => c, None => break, }; + + let head = desc_chain.head_index(); + if Self::is_head_in_flight(&self.inflight_requests, &batch_inflight_requests, head) { + warn!("Guest reused virtio-blk head_index {head} while the chain was used"); + return Err(Error::QueueDuplicatedHeadIndex); + } + let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref()) .map_err(Error::RequestParsing)?; @@ -384,10 +404,9 @@ impl BlockEpollHandler { fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> { match self.process_queue_submit() { Ok(()) => {} - Err(e @ Error::QueueIterator(_)) => { - // Iterator errors mean the guest virtqueue itself is corrupted. - // Surface the failure so the worker exits and spawn_virtio_thread - // marks the device as NEEDS_RESET. + Err(e @ (Error::QueueIterator(_) | Error::QueueDuplicatedHeadIndex)) => { + // Virtqueue is corrupted or guest driver is misbehaving; exit + // the worker so spawn_virtio_thread marks the device NEEDS_RESET. return Err(EpollHelperError::HandleEvent(anyhow!( "Failed to process queue (submit): {e}" )));