From 09459b6026dfe67cbd0c4f91e3addf8eab94936f Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:50:37 -0700 Subject: [PATCH 01/10] virtio-devices: introduce ActivationContext for device activation Signed-off-by: Peter Oskolkov (cherry picked from commit f77c6ef78bdca486eec0e6d81033af7082f9997d) --- fuzz/fuzz_targets/balloon.rs | 10 +++--- fuzz/fuzz_targets/block.rs | 10 +++--- fuzz/fuzz_targets/console.rs | 10 +++--- fuzz/fuzz_targets/iommu.rs | 10 +++--- fuzz/fuzz_targets/mem.rs | 10 +++--- fuzz/fuzz_targets/net.rs | 10 +++--- fuzz/fuzz_targets/pmem.rs | 10 +++--- fuzz/fuzz_targets/rng.rs | 10 +++--- fuzz/fuzz_targets/vsock.rs | 10 +++--- fuzz/fuzz_targets/watchdog.rs | 10 +++--- virtio-devices/src/balloon.rs | 13 ++++---- virtio-devices/src/block.rs | 12 +++---- virtio-devices/src/console.rs | 13 ++++---- virtio-devices/src/device.rs | 13 ++++---- virtio-devices/src/iommu.rs | 13 ++++---- virtio-devices/src/lib.rs | 4 +-- virtio-devices/src/mem.rs | 13 ++++---- virtio-devices/src/net.rs | 12 +++---- virtio-devices/src/pmem.rs | 13 ++++---- virtio-devices/src/rng.rs | 13 ++++---- .../src/transport/pci_common_config.rs | 12 ++----- virtio-devices/src/transport/pci_device.rs | 13 ++++---- virtio-devices/src/vdpa.rs | 13 ++++---- virtio-devices/src/vhost_user/blk.rs | 14 ++++----- virtio-devices/src/vhost_user/fs.rs | 14 ++++----- virtio-devices/src/vhost_user/net.rs | 15 ++++----- virtio-devices/src/vsock/device.rs | 31 ++++++++++--------- virtio-devices/src/watchdog.rs | 13 ++++---- vmm/src/device_manager.rs | 2 +- 29 files changed, 176 insertions(+), 170 deletions(-) diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index b745cf6187..edb4f0cf1a 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -95,15 +95,15 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { reporting_queue_evt.write(1).unwrap(); balloon - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ (0, inflate_q, inflate_evt), (1, deflate_q, deflate_evt), (2, reporting_q, reporting_evt), ], - ) + }) .ok(); // Wait for the events to finish and balloon device worker thread to return diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 7d1fbdf38f..108fb3fba3 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -89,11 +89,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); block - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and block device worker thread to return diff --git a/fuzz/fuzz_targets/console.rs b/fuzz/fuzz_targets/console.rs index 4b3a49df91..7f5fafaebc 100644 --- a/fuzz/fuzz_targets/console.rs +++ b/fuzz/fuzz_targets/console.rs @@ -128,11 +128,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { pipe_tx.write_all(console_input_bytes).unwrap(); // To use fuzzed data; console - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + }) .unwrap(); // Wait for the events to finish and console device worker thread to return diff --git a/fuzz/fuzz_targets/iommu.rs b/fuzz/fuzz_targets/iommu.rs index 8c9f26b262..791ab6b000 100644 --- a/fuzz/fuzz_targets/iommu.rs +++ b/fuzz/fuzz_targets/iommu.rs @@ -107,14 +107,14 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { request_queue_evt.write(1).unwrap(); iommu - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ (0, request_queue, request_evt), (0, _event_queue, _event_evt), ], - ) + }) .ok(); // Wait for the events to finish and vIOMMU device worker thread to return diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index 57fc9a91dd..46627e9315 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -105,11 +105,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); virtio_mem - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and virtio-mem device worker thread to return diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index 30968d2a47..0af835ac06 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -143,11 +143,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { input_queue_evt.write(1).unwrap(); output_queue_evt.write(1).unwrap(); - net.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], - ) + net.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + }) .unwrap(); // Wait for the events to finish and net device worker thread to return diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index a8fcb7a774..b42c20daea 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -61,11 +61,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { // Kick the 'queue' event before activate the pmem device queue_evt.write(1).unwrap(); - pmem.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + pmem.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and pmem device worker thread to return diff --git a/fuzz/fuzz_targets/rng.rs b/fuzz/fuzz_targets/rng.rs index 8d5ffe35b3..d9cd11f099 100644 --- a/fuzz/fuzz_targets/rng.rs +++ b/fuzz/fuzz_targets/rng.rs @@ -99,11 +99,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { // Kick the 'queue' event before activate the rng device queue_evt.write(1).unwrap(); - rng.activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + rng.activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and rng device worker thread to return diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 144b8b4057..72bdeb4d63 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -108,11 +108,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { .unwrap(); vsock - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and vsock device worker thread to return diff --git a/fuzz/fuzz_targets/watchdog.rs b/fuzz/fuzz_targets/watchdog.rs index f203a228f9..8736f8af3f 100644 --- a/fuzz/fuzz_targets/watchdog.rs +++ b/fuzz/fuzz_targets/watchdog.rs @@ -64,11 +64,11 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { queue_evt.write(1).unwrap(); watchdog - .activate( - guest_memory, - Arc::new(NoopVirtioInterrupt {}), - vec![(0, q, evt)], - ) + .activate(virtio_devices::ActivationContext { + mem: guest_memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![(0, q, evt)], + }) .ok(); // Wait for the events to finish and watchdog device worker thread to return diff --git a/virtio-devices/src/balloon.rs b/virtio-devices/src/balloon.rs index 3db6832617..bb9c46cbc8 100644 --- a/virtio-devices/src/balloon.rs +++ b/virtio-devices/src/balloon.rs @@ -590,12 +590,13 @@ impl VirtioDevice for Balloon { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 805a7c6155..0477ced070 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -1011,12 +1011,12 @@ impl VirtioDevice for Block { self.update_writeback(); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + } = context; // See if the guest didn't ack the device being read-only. // If so, warn and pretend it did. let original_acked_features = self.common.acked_features; diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index c8a9f08a02..760864b3a5 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -703,12 +703,13 @@ impl VirtioDevice for Console { self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.resizer .acked_features diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 8289cdf116..05c15e0add 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -54,6 +54,12 @@ pub struct VirtioSharedMemoryList { pub region_list: Vec, } +pub struct ActivationContext { + pub mem: GuestMemoryAtomic, + pub interrupt_cb: Arc, + pub queues: Vec<(usize, Queue, EventFd)>, +} + /// Trait for virtio devices to be driven by a virtio transport. /// /// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the @@ -95,12 +101,7 @@ pub trait VirtioDevice: Send { } /// Activates this device for real usage. - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_evt: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult; + fn activate(&mut self, context: ActivationContext) -> ActivateResult; /// Optionally deactivates this device and returns ownership of the guest memory map, interrupt /// event, and queue events. diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index f4812b04fb..87f8121696 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -1075,12 +1075,13 @@ impl VirtioDevice for Iommu { self.update_bypass(); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index d2d428299d..aae1ece387 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -42,8 +42,8 @@ pub use self::balloon::Balloon; pub use self::block::{Block, BlockState}; pub use self::console::{Console, ConsoleResizer, Endpoint}; pub use self::device::{ - DmaRemapping, PostMigrationAnnouncer, VirtioCommon, VirtioDevice, VirtioInterrupt, - VirtioInterruptType, VirtioSharedMemoryList, + ActivationContext, DmaRemapping, PostMigrationAnnouncer, VirtioCommon, VirtioDevice, + VirtioInterrupt, VirtioInterruptType, VirtioSharedMemoryList, }; pub use self::epoll_helper::{ EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, diff --git a/virtio-devices/src/mem.rs b/virtio-devices/src/mem.rs index 936fdbe42a..aed8ed48d2 100644 --- a/virtio-devices/src/mem.rs +++ b/virtio-devices/src/mem.rs @@ -950,12 +950,13 @@ impl VirtioDevice for Mem { self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 97e6349cec..a63b9978bc 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -827,12 +827,12 @@ impl VirtioDevice for Net { self.read_config_from_slice(config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let num_queues = queues.len(); diff --git a/virtio-devices/src/pmem.rs b/virtio-devices/src/pmem.rs index 549b62fd96..10cce76cb3 100644 --- a/virtio-devices/src/pmem.rs +++ b/virtio-devices/src/pmem.rs @@ -377,12 +377,13 @@ impl VirtioDevice for Pmem { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(disk) = self.disk.as_ref() { diff --git a/virtio-devices/src/rng.rs b/virtio-devices/src/rng.rs index 2f980d4d8b..a3412a9e07 100644 --- a/virtio-devices/src/rng.rs +++ b/virtio-devices/src/rng.rs @@ -244,12 +244,13 @@ impl VirtioDevice for Rng { self.common.ack_features(value); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index 07f5b4fc0f..d75e7c054e 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -404,11 +404,8 @@ impl Snapshottable for VirtioPciCommonConfig { #[cfg(test)] mod unit_tests { - use vm_memory::GuestMemoryAtomic; - use vmm_sys_util::eventfd::EventFd; - use super::*; - use crate::{ActivateResult, GuestMemoryMmap, VirtioInterrupt}; + use crate::{ActivateResult, ActivationContext}; struct DummyDevice(u32); const QUEUE_SIZE: u16 = 256; @@ -421,12 +418,7 @@ mod unit_tests { fn queue_max_sizes(&self) -> &[u16] { QUEUE_SIZES } - fn activate( - &mut self, - _mem: GuestMemoryAtomic, - _interrupt_evt: Arc, - _queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, _context: ActivationContext) -> ActivateResult { Ok(()) } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 408611e29a..a0d244bcb7 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -291,12 +291,13 @@ pub struct VirtioPciDeviceActivator { } impl VirtioPciDeviceActivator { - pub fn activate(&mut self) -> ActivateResult { - self.device.lock().unwrap().activate( - self.memory.take().unwrap(), - self.interrupt.take().unwrap(), - self.queues.take().unwrap(), - )?; + pub fn activate(mut self) -> ActivateResult { + let mut locked_device = self.device.lock().unwrap(); + locked_device.activate(crate::device::ActivationContext { + mem: self.memory.take().unwrap(), + interrupt_cb: self.interrupt.take().unwrap(), + queues: self.queues.take().unwrap(), + })?; self.device_activated.store(true, Ordering::SeqCst); if let Some(barrier) = self.barrier.take() { diff --git a/virtio-devices/src/vdpa.rs b/virtio-devices/src/vdpa.rs index 725f215c77..1996d94470 100644 --- a/virtio-devices/src/vdpa.rs +++ b/virtio-devices/src/vdpa.rs @@ -428,12 +428,13 @@ impl VirtioDevice for Vdpa { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - virtio_interrupt: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb: virtio_interrupt, + queues, + .. + } = context; self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues) .map_err(ActivateError::ActivateVdpa)?; diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index d26350c91a..ea52872aa1 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -19,7 +19,6 @@ use virtio_bindings::virtio_blk::{ VIRTIO_BLK_F_GEOMETRY, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX, VIRTIO_BLK_F_SIZE_MAX, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_WRITE_ZEROES, }; -use virtio_queue::Queue; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -279,12 +278,13 @@ impl VirtioDevice for Blk { } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vhost_user/fs.rs b/virtio-devices/src/vhost_user/fs.rs index d0005af90f..ca8c72539d 100644 --- a/virtio-devices/src/vhost_user/fs.rs +++ b/virtio-devices/src/vhost_user/fs.rs @@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize}; use serde_with::{Bytes, serde_as}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; -use virtio_queue::Queue; use vm_device::UserspaceMapping; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; @@ -261,12 +260,13 @@ impl VirtioDevice for Fs { self.read_config_from_slice(self.config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 667c3747b2..4b77b4f91c 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -20,7 +20,7 @@ use virtio_bindings::virtio_net::{ VIRTIO_NET_F_STATUS, VIRTIO_NET_S_ANNOUNCE, VIRTIO_NET_S_LINK_UP, }; use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; -use virtio_queue::{Queue, QueueT}; +use virtio_queue::QueueT; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; @@ -372,12 +372,13 @@ impl VirtioDevice for Net { self.read_config_from_slice(config.as_slice(), offset, data); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 27a0af1ff2..9acea8d707 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -435,12 +435,13 @@ where } } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); @@ -593,9 +594,11 @@ mod unit_tests { let memory = GuestMemoryAtomic::new(ctx.mem.clone()); // Test a bad activation. - let bad_activate = - ctx.device - .activate(memory.clone(), Arc::new(NoopVirtioInterrupt {}), Vec::new()); + let bad_activate = ctx.device.activate(crate::device::ActivationContext { + mem: memory.clone(), + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: Vec::new(), + }); match bad_activate { Err(ActivateError::BadActivate) => (), other => panic!("{other:?}"), @@ -603,10 +606,10 @@ mod unit_tests { // Test a correct activation. ctx.device - .activate( - memory, - Arc::new(NoopVirtioInterrupt {}), - vec![ + .activate(crate::device::ActivationContext { + mem: memory, + interrupt_cb: Arc::new(NoopVirtioInterrupt {}), + queues: vec![ ( 0, Queue::new(256).unwrap(), @@ -623,7 +626,7 @@ mod unit_tests { EventFd::new(EFD_NONBLOCK).unwrap(), ), ], - ) + }) .unwrap(); } diff --git a/virtio-devices/src/watchdog.rs b/virtio-devices/src/watchdog.rs index 6b9f7cc0ac..742a2e0241 100644 --- a/virtio-devices/src/watchdog.rs +++ b/virtio-devices/src/watchdog.rs @@ -326,12 +326,13 @@ impl VirtioDevice for Watchdog { self.common.ack_features(value); } - fn activate( - &mut self, - mem: GuestMemoryAtomic, - interrupt_cb: Arc, - mut queues: Vec<(usize, Queue, EventFd)>, - ) -> ActivateResult { + fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { + let crate::device::ActivationContext { + mem, + interrupt_cb, + mut queues, + .. + } = context; self.common.activate(&queues, interrupt_cb.clone())?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 1e6293c350..8911e8e405 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -4566,7 +4566,7 @@ impl DeviceManager { } pub fn activate_virtio_devices(&self) -> DeviceManagerResult<()> { - for mut activator in self.pending_activations.lock().unwrap().drain(..) { + for activator in self.pending_activations.lock().unwrap().drain(..) { activator .activate() .map_err(DeviceManagerError::VirtioActivate)?; From d8caff6c9708498f836acb068f133e16fb2986da Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:51:50 -0700 Subject: [PATCH 02/10] virtio-devices: switch driver_status to Arc Signed-off-by: Peter Oskolkov (cherry picked from commit 21bd3ae91661104a7c58662316b4538eac09fbc7) --- virtio-devices/src/transport/pci_common_config.rs | 14 +++++++------- virtio-devices/src/transport/pci_device.rs | 10 ++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/virtio-devices/src/transport/pci_common_config.rs b/virtio-devices/src/transport/pci_common_config.rs index d75e7c054e..ec4542948f 100644 --- a/virtio-devices/src/transport/pci_common_config.rs +++ b/virtio-devices/src/transport/pci_common_config.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::atomic::{AtomicU8, AtomicU16, Ordering}; use std::sync::{Arc, Mutex}; use byteorder::{ByteOrder, LittleEndian}; @@ -125,7 +125,7 @@ pub fn get_vring_size(t: VringType, queue_size: u16) -> u64 { /// le64 queue_used; // 0x30 // read-write pub struct VirtioPciCommonConfig { pub access_platform: Option>, - pub driver_status: u8, + pub driver_status: Arc, pub config_generation: u8, pub device_feature_select: u32, pub driver_feature_select: u32, @@ -141,7 +141,7 @@ impl VirtioPciCommonConfig { ) -> Self { VirtioPciCommonConfig { access_platform, - driver_status: state.driver_status, + driver_status: Arc::new(AtomicU8::new(state.driver_status)), config_generation: state.config_generation, device_feature_select: state.device_feature_select, driver_feature_select: state.driver_feature_select, @@ -153,7 +153,7 @@ impl VirtioPciCommonConfig { fn state(&self) -> VirtioPciCommonConfigState { VirtioPciCommonConfigState { - driver_status: self.driver_status, + driver_status: self.driver_status.load(Ordering::Acquire), config_generation: self.config_generation, device_feature_select: self.device_feature_select, driver_feature_select: self.driver_feature_select, @@ -223,7 +223,7 @@ impl VirtioPciCommonConfig { debug!("read_common_config_byte: offset 0x{offset:x}"); // The driver is only allowed to do aligned, properly sized access. match offset { - 0x14 => self.driver_status, + 0x14 => self.driver_status.load(Ordering::Acquire), 0x15 => self.config_generation, _ => { warn!("invalid virtio config byte read: 0x{offset:x}"); @@ -235,7 +235,7 @@ impl VirtioPciCommonConfig { fn write_common_config_byte(&mut self, offset: u64, value: u8) { debug!("write_common_config_byte: offset 0x{offset:x}"); match offset { - 0x14 => self.driver_status = value, + 0x14 => self.driver_status.store(value, Ordering::Release), _ => { warn!("invalid virtio config byte write: 0x{offset:x}"); } @@ -437,7 +437,7 @@ mod unit_tests { fn write_base_regs() { let mut regs = VirtioPciCommonConfig { access_platform: None, - driver_status: 0xaa, + driver_status: Arc::new(AtomicU8::new(0xaa)), config_generation: 0x55, device_feature_select: 0x0, driver_feature_select: 0x0, diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index a0d244bcb7..08b0506938 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -642,13 +642,13 @@ impl VirtioPciDevice { fn is_driver_ready(&self) -> bool { let ready_bits = (DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8; - self.common_config.driver_status == ready_bits - && self.common_config.driver_status & DEVICE_FAILED as u8 == 0 + let driver_status = self.common_config.driver_status.load(Ordering::SeqCst); + driver_status == ready_bits && (driver_status & DEVICE_FAILED as u8) == 0 } /// Determines if the driver has requested the device (re)init / reset itself fn is_driver_init(&self) -> bool { - self.common_config.driver_status == DEVICE_INIT as u8 + self.common_config.driver_status.load(Ordering::SeqCst) == DEVICE_INIT as u8 } pub fn config_bar_addr(&self) -> u64 { @@ -1220,7 +1220,9 @@ impl PciDevice for VirtioPciDevice { self.common_config.queue_select = 0; } else { error!("Attempt to reset device when not implemented in underlying device"); - self.common_config.driver_status = crate::DEVICE_FAILED as u8; + self.common_config + .driver_status + .store(crate::DEVICE_FAILED as u8, Ordering::SeqCst); } } From 8cfdf856cb74f665bfc4d18ac40ab786524addb2 Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:55:03 -0700 Subject: [PATCH 03/10] virtio-devices: wire driver_status to EpollHandler Signed-off-by: Peter Oskolkov (cherry picked from commit b5053ae4dededfda406bc36c8c1956c4fc0ed704) --- fuzz/fuzz_targets/balloon.rs | 1 + fuzz/fuzz_targets/block.rs | 1 + fuzz/fuzz_targets/console.rs | 1 + fuzz/fuzz_targets/iommu.rs | 1 + fuzz/fuzz_targets/mem.rs | 1 + fuzz/fuzz_targets/net.rs | 1 + fuzz/fuzz_targets/pmem.rs | 1 + fuzz/fuzz_targets/rng.rs | 1 + fuzz/fuzz_targets/vsock.rs | 1 + fuzz/fuzz_targets/watchdog.rs | 1 + virtio-devices/src/block.rs | 9 ++++++++- virtio-devices/src/device.rs | 3 ++- virtio-devices/src/net.rs | 10 +++++++++- virtio-devices/src/transport/pci_device.rs | 5 ++++- virtio-devices/src/vsock/device.rs | 2 ++ 15 files changed, 35 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/balloon.rs b/fuzz/fuzz_targets/balloon.rs index edb4f0cf1a..58b9b30582 100644 --- a/fuzz/fuzz_targets/balloon.rs +++ b/fuzz/fuzz_targets/balloon.rs @@ -103,6 +103,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { (1, deflate_q, deflate_evt), (2, reporting_q, reporting_evt), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/block.rs b/fuzz/fuzz_targets/block.rs index 108fb3fba3..12a4dcc726 100644 --- a/fuzz/fuzz_targets/block.rs +++ b/fuzz/fuzz_targets/block.rs @@ -93,6 +93,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/console.rs b/fuzz/fuzz_targets/console.rs index 7f5fafaebc..e27331ed01 100644 --- a/fuzz/fuzz_targets/console.rs +++ b/fuzz/fuzz_targets/console.rs @@ -132,6 +132,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); diff --git a/fuzz/fuzz_targets/iommu.rs b/fuzz/fuzz_targets/iommu.rs index 791ab6b000..a10640487f 100644 --- a/fuzz/fuzz_targets/iommu.rs +++ b/fuzz/fuzz_targets/iommu.rs @@ -114,6 +114,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { (0, request_queue, request_evt), (0, _event_queue, _event_evt), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index 46627e9315..73ec11b025 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -109,6 +109,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/net.rs b/fuzz/fuzz_targets/net.rs index 0af835ac06..df9a1dce5a 100644 --- a/fuzz/fuzz_targets/net.rs +++ b/fuzz/fuzz_targets/net.rs @@ -147,6 +147,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); diff --git a/fuzz/fuzz_targets/pmem.rs b/fuzz/fuzz_targets/pmem.rs index b42c20daea..0bd083a1c2 100644 --- a/fuzz/fuzz_targets/pmem.rs +++ b/fuzz/fuzz_targets/pmem.rs @@ -65,6 +65,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/rng.rs b/fuzz/fuzz_targets/rng.rs index d9cd11f099..13548664a8 100644 --- a/fuzz/fuzz_targets/rng.rs +++ b/fuzz/fuzz_targets/rng.rs @@ -103,6 +103,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/vsock.rs b/fuzz/fuzz_targets/vsock.rs index 72bdeb4d63..33ebe78886 100644 --- a/fuzz/fuzz_targets/vsock.rs +++ b/fuzz/fuzz_targets/vsock.rs @@ -112,6 +112,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/fuzz/fuzz_targets/watchdog.rs b/fuzz/fuzz_targets/watchdog.rs index 8736f8af3f..31361755df 100644 --- a/fuzz/fuzz_targets/watchdog.rs +++ b/fuzz/fuzz_targets/watchdog.rs @@ -68,6 +68,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus { mem: guest_memory, interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: vec![(0, q, evt)], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .ok(); diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 0477ced070..d49ec49257 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -13,7 +13,7 @@ use std::num::Wrapping; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering}; use std::sync::{Arc, Barrier}; use std::{io, result}; @@ -161,6 +161,8 @@ struct BlockEpollHandler { host_cpus: Option>, acked_features: u64, disable_sector0_writes: bool, + #[allow(unused)] + device_status: Arc, } fn has_feature(features: u64, feature_flag: u64) -> bool { @@ -662,6 +664,7 @@ pub struct Block { serial: Vec, queue_affinity: BTreeMap>, disable_sector0_writes: bool, + device_status: Arc, } #[derive(Serialize, Deserialize)] @@ -820,6 +823,7 @@ impl Block { serial, queue_affinity, disable_sector0_writes, + device_status: Arc::new(AtomicU8::new(0)), }) } @@ -1016,7 +1020,9 @@ impl VirtioDevice for Block { mem, interrupt_cb, mut queues, + device_status, } = context; + self.device_status = device_status; // See if the guest didn't ack the device being read-only. // If so, warn and pretend it did. let original_acked_features = self.common.acked_features; @@ -1078,6 +1084,7 @@ impl VirtioDevice for Block { host_cpus: self.queue_affinity.get(&queue_idx).cloned(), acked_features: self.common.acked_features, disable_sector0_writes: self.disable_sector0_writes, + device_status: self.device_status.clone(), }; let paused = self.common.paused.clone(); diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 05c15e0add..90958ecddc 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::io::Write; use std::num::Wrapping; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; @@ -58,6 +58,7 @@ pub struct ActivationContext { pub mem: GuestMemoryAtomic, pub interrupt_cb: Arc, pub queues: Vec<(usize, Queue, EventFd)>, + pub device_status: Arc, } /// Trait for virtio devices to be driven by a virtio transport. diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index a63b9978bc..e16d97765d 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -10,7 +10,7 @@ use std::net::IpAddr; use std::num::Wrapping; use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering}; use std::sync::{Arc, Barrier}; use std::{result, thread}; @@ -176,6 +176,8 @@ struct NetEpollHandler { queue_index_base: u16, queue_pair: (Queue, Queue), queue_evt_pair: (EventFd, EventFd), + #[allow(unused)] + device_status: Arc, } impl NetEpollHandler { @@ -411,6 +413,7 @@ pub struct Net { seccomp_action: SeccompAction, rate_limiter_config: Option, exit_evt: EventFd, + device_status: Arc, } #[derive(Serialize, Deserialize)] @@ -577,6 +580,7 @@ impl Net { seccomp_action, rate_limiter_config, exit_evt, + device_status: Arc::new(AtomicU8::new(0)), }) } @@ -832,7 +836,9 @@ impl VirtioDevice for Net { mem, interrupt_cb, mut queues, + device_status, } = context; + self.device_status = device_status; self.common.activate(&queues, interrupt_cb.clone())?; let num_queues = queues.len(); @@ -946,6 +952,7 @@ impl VirtioDevice for Net { interrupt_cb: interrupt_cb.clone(), kill_evt, pause_evt, + device_status: self.device_status.clone(), }; let paused = self.common.paused.clone(); @@ -1170,6 +1177,7 @@ mod unit_tests { seccomp_action: SeccompAction::Allow, rate_limiter_config: None, exit_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(), + device_status: Arc::new(Default::default()), } } diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index 08b0506938..c4df68c999 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -10,7 +10,7 @@ use std::any::Any; use std::cmp; use std::io::Write; use std::ops::Deref; -use std::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicUsize, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; @@ -288,6 +288,7 @@ pub struct VirtioPciDeviceActivator { queues: Option>, barrier: Option>, id: String, + status: Arc, } impl VirtioPciDeviceActivator { @@ -297,6 +298,7 @@ impl VirtioPciDeviceActivator { mem: self.memory.take().unwrap(), interrupt_cb: self.interrupt.take().unwrap(), queues: self.queues.take().unwrap(), + device_status: self.status, })?; self.device_activated.store(true, Ordering::SeqCst); @@ -802,6 +804,7 @@ impl VirtioPciDevice { device_activated: self.device_activated.clone(), barrier, id: self.id.clone(), + status: self.common_config.driver_status.clone(), } } diff --git a/virtio-devices/src/vsock/device.rs b/virtio-devices/src/vsock/device.rs index 9acea8d707..bfb3e0c142 100644 --- a/virtio-devices/src/vsock/device.rs +++ b/virtio-devices/src/vsock/device.rs @@ -598,6 +598,7 @@ mod unit_tests { mem: memory.clone(), interrupt_cb: Arc::new(NoopVirtioInterrupt {}), queues: Vec::new(), + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }); match bad_activate { Err(ActivateError::BadActivate) => (), @@ -626,6 +627,7 @@ mod unit_tests { EventFd::new(EFD_NONBLOCK).unwrap(), ), ], + device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)), }) .unwrap(); } From bed329ed50b3ef7a4ef505eede9bfc2ff876338c Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:56:49 -0700 Subject: [PATCH 04/10] virtio-devices: net: handle corrupted requests with NEEDS_RESET A buggy or malicious guest may write an inappropriate value into virtqueue's next_avail field. This will result in an error when iterating over the queue: https://github.com/rust-vmm/vm-virtio/blob/863837ef863f6880bb8357e60bbac49e72c0844c/virtio-queue/src/queue.rs#L708 but this error is (logged and) ignored if pop_descriptor_chain() is used: https://github.com/rust-vmm/vm-virtio/blob/863837ef863f6880bb8357e60bbac49e72c0844c/virtio-queue/src/queue.rs#L583 A reasonable approach, implemented here, is to mark the device as NEEDS_RESET and ignore further queue events until the guest reinitializes the device. How this patch was tested: Linux kernel was patched to trigger a bad next_avail when the virtqueue queue counter reaches 5000: --------------- START OF LINUX KERNEL PATCH ---------- $ git diff diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index b784aab668670..989f2a0c64a77 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -15,6 +15,9 @@ #include #include + +void virtqueue_kick_always(struct virtqueue *vq); + #ifdef DEBUG /* For development, we want to crash whenever the ring is screwed. */ #define BAD_RING(_vq, fmt, args...) \ @@ -677,6 +680,12 @@ static inline int virtqueue_add_split( struct virtqueue *_vq, * new available array entries. */ virtio_wmb(vq->weak_barriers); vq->split.avail_idx_shadow++; + { + if ((vq->split.avail_idx_shadow % 100) == 0) + printk(KERN_ERR "avail idx: %d", + (int)vq->split.avail_idx_shadow); + if (vq->split.avail_idx_shadow == 5000) + vq->split.avail_idx_shadow = 0; + } vq->split.vring.avail->idx = cpu_to_virtio16(_vq->vdev, vq->split.avail_idx_shadow); vq->num_added++; @@ -689,6 +698,11 @@ static inline int virtqueue_add_split( struct virtqueue *_vq, if (unlikely(vq->num_added == (1 << 16) - 1)) virtqueue_kick(_vq); + { + if (unlikely(vq->split.avail_idx_shadow == 0)) + virtqueue_kick_always(_vq); + } + return 0; unmap_release: @@ -2515,6 +2529,11 @@ bool virtqueue_kick(struct virtqueue *vq) } EXPORT_SYMBOL_GPL(virtqueue_kick); +void virtqueue_kick_always(struct virtqueue *vq) +{ + virtqueue_kick_prepare(vq); + virtqueue_notify(vq); +} /** * virtqueue_get_buf_ctx - get the next used buffer * @_vq: the struct virtqueue we're talking about. --------------- END OF LINUX KERNEL PATCH ---------- Then the kernel was booted, and the host pinged until the nic became unresponsive: ping -i 0.002 192.168.4.1 Device status was confirmed using cat /sys/class/net/eth0/device/status (it was 0x4f). Then the device was re-initialized: DEV_NAME=$(basename $(readlink -f /sys/class/net/eth0/device)) echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/unbind echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/bind ip link set eth0 up At this point networking became healthly again. Signed-off-by: Peter Oskolkov (cherry picked from commit 563303b50a6d4a06b0660840887684d6167da3c2) --- net_util/src/queue_pair.rs | 16 ++++++++-- virtio-devices/src/lib.rs | 1 + virtio-devices/src/net.rs | 63 ++++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/net_util/src/queue_pair.rs b/net_util/src/queue_pair.rs index 86a1c758dc..c0b8825e71 100644 --- a/net_util/src/queue_pair.rs +++ b/net_util/src/queue_pair.rs @@ -51,7 +51,13 @@ impl TxVirtio { let mut retry_write = false; let mut rate_limit_reached = false; - while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) { + loop { + let mut iter = queue + .iter(mem) + .map_err(NetQueuePairError::QueueIteratorFailed)?; + let Some(mut desc_chain) = iter.next() else { + break; + }; if rate_limit_reached { queue.go_to_previous_position(); break; @@ -180,7 +186,13 @@ impl RxVirtio { let mut exhausted_descs = true; let mut rate_limit_reached = false; - while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) { + loop { + let mut iter = queue + .iter(mem) + .map_err(NetQueuePairError::QueueIteratorFailed)?; + let Some(mut desc_chain) = iter.next() else { + break; + }; if rate_limit_reached { exhausted_descs = false; queue.go_to_previous_position(); diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index aae1ece387..7084879a06 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -66,6 +66,7 @@ const DEVICE_ACKNOWLEDGE: u32 = 0x01; const DEVICE_DRIVER: u32 = 0x02; const DEVICE_DRIVER_OK: u32 = 0x04; const DEVICE_FEATURES_OK: u32 = 0x08; +const DEVICE_NEEDS_RESET: u32 = 0x40; const DEVICE_FAILED: u32 = 0x80; const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28; diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index e16d97765d..9d5c2f7799 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -176,7 +176,6 @@ struct NetEpollHandler { queue_index_base: u16, queue_pair: (Queue, Queue), queue_evt_pair: (EventFd, EventFd), - #[allow(unused)] device_status: Arc, } @@ -191,6 +190,9 @@ impl NetEpollHandler { } fn handle_rx_event(&mut self) -> result::Result<(), DeviceError> { + if self.needs_reset() { + return Ok(()); + } let queue_evt = &self.queue_evt_pair.0; if let Err(e) = queue_evt.read() { error!("Failed to get rx queue event: {e:?}"); @@ -219,12 +221,43 @@ impl NetEpollHandler { Ok(()) } + fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) { + // The guest submitted a corrupted VirtQ request, and the error + // was logged during queue processing. We cannot just ignore the + // error, as the guest could continue spamming the VMM with bad + // requests, triggering excessive error logging. So we mark + // the device "NEEDS_RESET", effectively stopping all request + // processing (see self.needs_reset() usage) until the guest + // resets and reactivates the device. + + warn!( + "Corrupted request detected (virtqueue error: {err:?}). \ +Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." + ); + + self.device_status + .fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); + + // Let the guest know that the device status has changed. + if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) { + error!("Failed to signal config interrupt: {e:?}"); + } + } + fn process_tx(&mut self) -> result::Result<(), DeviceError> { - if self + if self.needs_reset() { + return Ok(()); + } + let res = self .net - .process_tx(&self.mem.memory(), &mut self.queue_pair.1) - .map_err(DeviceError::NetQueuePair)? - { + .process_tx(&self.mem.memory(), &mut self.queue_pair.1); + + if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + + if res.map_err(DeviceError::NetQueuePair)? { self.signal_used_queue(self.queue_index_base + 1)?; debug!("Signalling TX queue"); } else { @@ -248,11 +281,19 @@ impl NetEpollHandler { } fn handle_rx_tap_event(&mut self) -> result::Result<(), DeviceError> { - if self + if self.needs_reset() { + return Ok(()); + } + let res = self .net - .process_rx(&self.mem.memory(), &mut self.queue_pair.0) - .map_err(DeviceError::NetQueuePair)? - { + .process_rx(&self.mem.memory(), &mut self.queue_pair.0); + + if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + + if res.map_err(DeviceError::NetQueuePair)? { self.signal_used_queue(self.queue_index_base)?; trace!("Signalling RX queue"); } else { @@ -302,6 +343,10 @@ impl NetEpollHandler { Ok(()) } + + fn needs_reset(&self) -> bool { + (self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0 + } } impl EpollHelperHandler for NetEpollHandler { From c7602739cf90883cfda0a1720c59aef4f188fc7a Mon Sep 17 00:00:00 2001 From: Peter Oskolkov Date: Thu, 12 Mar 2026 09:58:15 -0700 Subject: [PATCH 05/10] virtio-devices: block: handle corrupted requests with NEEDS_RESET Signed-off-by: Peter Oskolkov (cherry picked from commit 8b60b38281f95a519802e21358080582c28c46ef) --- virtio-devices/src/block.rs | 46 +++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index d49ec49257..95304bb3d7 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -161,7 +161,6 @@ struct BlockEpollHandler { host_cpus: Option>, acked_features: u64, disable_sector0_writes: bool, - #[allow(unused)] device_status: Arc, } @@ -170,6 +169,10 @@ fn has_feature(features: u64, feature_flag: u64) -> bool { } impl BlockEpollHandler { + fn needs_reset(&self) -> bool { + (self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0 + } + fn check_request( features: u64, request: &Request, @@ -194,12 +197,48 @@ impl BlockEpollHandler { Ok(()) } + fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) { + // The guest submitted a corrupted VirtQ request, and the error + // was logged during queue processing. We cannot just ignore the + // error, as the guest could continue spamming the VMM with bad + // requests, triggering excessive error logging. So we mark + // the device "NEEDS_RESET", effectively stopping all request + // processing (see self.needs_reset() usage) until the guest + // resets and reactivates the device. + + warn!( + "Corrupted request detected (virtqueue error: {err:?}). \ +Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." + ); + + self.device_status + .fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); + + // Let the guest know that the device status has changed. + if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) { + error!("Failed to signal config interrupt: {e:?}"); + } + } + fn process_queue_submit(&mut self) -> Result<()> { + if self.needs_reset() { + return Ok(()); + } let queue = &mut self.queue; let mut batch_requests = Vec::new(); let mut batch_inflight_requests = Vec::new(); - while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { + loop { + let mut desc_chain = match queue.iter(self.mem.memory()) { + Ok(mut iter) => match iter.next() { + Some(c) => c, + None => break, + }, + Err(err) => { + self.handle_queue_iterator_error(&err); + return Ok(()); + } + }; let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref()) .map_err(Error::RequestParsing)?; @@ -382,6 +421,9 @@ impl BlockEpollHandler { } fn process_queue_complete(&mut self) -> Result<()> { + if self.needs_reset() { + return Ok(()); + } let mem = self.mem.memory(); let mut read_bytes = Wrapping(0); let mut write_bytes = Wrapping(0); From 34b1fd2d9df6649ad7f49ddeb6cb57aba0151d58 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 16:10:15 -0700 Subject: [PATCH 06/10] 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 (cherry picked from commit 544fa4aa764abae9d7cbe53c69598521570360a8) --- virtio-devices/src/block.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 95304bb3d7..92bef5ba57 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -211,6 +211,10 @@ impl BlockEpollHandler { Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." ); + self.set_needs_reset(); + } + + fn set_needs_reset(&mut self) { self.device_status .fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); @@ -220,6 +224,17 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese } } + // 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<()> { if self.needs_reset() { return Ok(()); @@ -239,6 +254,14 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese return Ok(()); } }; + + 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"); + self.set_needs_reset(); + return Ok(()); + } + let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref()) .map_err(Error::RequestParsing)?; From a864ff6119a2c65c1f3624448baa8dc41ea532bc Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 17:09:06 -0700 Subject: [PATCH 07/10] virtio-devices: block: track non-batch inflight reqs immediately For non-batch backends execute_async submits the kernel I/O inline before returning. An early return while processing before inserting in inflight_requests, meant the request went untracked, the local batch list was never appended to inflight_requests, even though the request is pending in the kernel. To track it, insert into self.inflight_requests as soon as execute_async returns Ok. The completion path's find_inflight_request now matches the orphan and the bounce buffer is freed only after the kernel signals it is done. Signed-off-by: Dylan Reid (cherry picked from commit fa8acbd712ebf6574938191aaf846001e3be8d1e) --- virtio-devices/src/block.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 92bef5ba57..328df0e6dd 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -346,8 +346,11 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese ) } } + batch_inflight_requests.push((desc_chain.head_index(), request)); + } else { + self.inflight_requests + .push_back((desc_chain.head_index(), request)); } - batch_inflight_requests.push((desc_chain.head_index(), request)); } else { let status = match result { Ok(_) => VIRTIO_BLK_S_OK, From 6079e408ef7d89c44996b005ffa968db44fc575a Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 8 May 2026 10:36:14 +0200 Subject: [PATCH 08/10] block: AlignedOperation owns its bounce buffer via Drop The bounce buffer for an unaligned descriptor was allocated in execute_async and leaked on error paths, even though, for the sync case the kernel already had a pointer to the buffer. Clean this up by moving ownership of the buffer to the AlignedOperation type. To make it actually safe, stop stashing a guest memory pointer for the duration of the op. Instead, save the guest address and pass guest memory back to the complete function. Signed-off-by: Dylan Reid (cherry picked from commit 1b8c92dd5e3c0c58316826486ce5ee30eeb71407)) [backport: adapted to stable/v51.x; v51.x has no block/src/request.rs split, so the new aligned_operation module is added next to block/src/lib.rs and the in tree struct, alloc, free path is replaced in place.] Signed-off-by: Anatol Belski --- block/src/aligned_operation.rs | 90 ++++++++++++++++++++++++++++++++++ block/src/lib.rs | 66 +++++++------------------ virtio-devices/src/block.rs | 4 +- 3 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 block/src/aligned_operation.rs diff --git a/block/src/aligned_operation.rs b/block/src/aligned_operation.rs new file mode 100644 index 0000000000..3081b7b705 --- /dev/null +++ b/block/src/aligned_operation.rs @@ -0,0 +1,90 @@ +// Copyright (c) 2026 Meta Platforms, Inc. and affiliates. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use std::alloc::{Layout, alloc_zeroed, dealloc}; +use std::io; + +use vm_memory::GuestAddress; + +/// Owns an aligned bounce buffer used when a guest descriptor's host VA +/// does not meet the disk backend's alignment requirement. +#[derive(Debug)] +pub struct AlignedOperation { + data_addr: GuestAddress, + aligned_ptr: *mut u8, + size: usize, + layout: Layout, +} + +impl AlignedOperation { + /// Allocate a zero-initialized buffer of `size` bytes aligned to + /// `alignment`. Returns `InvalidInput` if `size` is zero; + /// `alignment` must be a power of two and not exceed `isize::MAX` + /// after rounding up. + pub fn new(data_addr: GuestAddress, size: usize, alignment: usize) -> io::Result { + if size == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "AlignedOperation requires a non-zero size", + )); + } + let layout = Layout::from_size_align(size, alignment) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + // SAFETY: size is non-zero (checked above) and Layout::from_size_align + // rejects alignments that are not a power of two or that overflow. + let aligned_ptr = unsafe { alloc_zeroed(layout) }; + if aligned_ptr.is_null() { + return Err(io::Error::last_os_error()); + } + Ok(Self { + data_addr, + aligned_ptr, + size, + layout, + }) + } + + /// Gets the raw pointer to the aligned buffer. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.aligned_ptr + } + + /// Returns the aligned buffer as a slice. + pub fn as_bytes(&self) -> &[u8] { + // SAFETY: `new` allocates `size` bytes via alloc_zeroed (so they + // are initialized) and AlignedOperation owns the buffer + // exclusively. + unsafe { std::slice::from_raw_parts(self.aligned_ptr, self.size) } + } + + /// Returns the aligned buffer as a mutable slice. + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + // SAFETY: same invariant as as_bytes; &mut self rules out other + // simultaneous borrows. + unsafe { std::slice::from_raw_parts_mut(self.aligned_ptr, self.size) } + } + + /// Returns the guest address for this op. + pub fn data_addr(&self) -> GuestAddress { + self.data_addr + } +} + +impl Drop for AlignedOperation { + fn drop(&mut self) { + // SAFETY: `new` is the only constructor, and it stores a pointer + // returned by `alloc_zeroed` paired with the exact `layout` used + // for that allocation. Ownership has not escaped (the type is + // neither `Clone` nor `Copy`). + unsafe { + dealloc(self.aligned_ptr, self.layout); + } + } +} + +// SAFETY: AlignedOperation owns its heap allocation exclusively (no Clone/ +// Copy, no shared aliases) and the allocation's lifetime is tied to the +// value's. Moving an AlignedOperation between threads transfers that +// ownership; the same rationale Box uses for its Send impl. +unsafe impl Send for AlignedOperation {} diff --git a/block/src/lib.rs b/block/src/lib.rs index 9f78cefd9e..504f4cc3c2 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -8,6 +8,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +mod aligned_operation; pub mod async_io; pub mod fcntl; pub mod fixed_vhd; @@ -28,7 +29,7 @@ pub mod vhd; pub mod vhdx; pub mod vhdx_sync; -use std::alloc::{Layout, alloc_zeroed, dealloc}; +use std::alloc::{Layout, alloc_zeroed}; use std::collections::VecDeque; use std::fmt::{self, Debug}; use std::fs::File; @@ -40,6 +41,7 @@ use std::str::FromStr; use std::time::Instant; use std::{cmp, result}; +pub use aligned_operation::AlignedOperation; #[cfg(feature = "io_uring")] use io_uring::{IoUring, Probe, opcode}; use libc::{S_IFBLK, S_IFMT, ioctl}; @@ -232,14 +234,6 @@ fn sector( const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32; -#[derive(Debug)] -pub struct AlignedOperation { - origin_ptr: u64, - aligned_ptr: u64, - size: usize, - layout: Layout, -} - pub struct BatchRequest { pub offset: libc::off_t, pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>, @@ -473,31 +467,19 @@ impl Request { let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) { origin_ptr.as_ptr() as *mut libc::c_void } else { - let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap(); - // SAFETY: layout has non-zero size - let aligned_ptr = unsafe { alloc_zeroed(layout) }; - if aligned_ptr.is_null() { - return Err(ExecuteError::TemporaryBufferAllocation( - io::Error::last_os_error(), - )); - } + let mut aligned_op = + AlignedOperation::new(data_addr, data_len, SECTOR_SIZE as usize) + .map_err(ExecuteError::TemporaryBufferAllocation)?; // We need to perform the copy beforehand in case we're writing // data out. if request_type == RequestType::Out { - // SAFETY: destination buffer has been allocated with - // the proper size. - unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) }; + mem.read_slice(aligned_op.as_bytes_mut(), data_addr) + .map_err(ExecuteError::Read)?; } - // Store both origin and aligned pointers for complete_async() - // to process them. - self.aligned_operations.push(AlignedOperation { - origin_ptr: origin_ptr.as_ptr() as u64, - aligned_ptr: aligned_ptr as u64, - size: data_len, - layout, - }); + let aligned_ptr = aligned_op.as_mut_ptr(); + self.aligned_operations.push(aligned_op); aligned_ptr as *mut libc::c_void }; @@ -639,31 +621,17 @@ impl Request { Ok(ret) } - pub fn complete_async(&mut self) -> result::Result<(), Error> { - for aligned_operation in self.aligned_operations.drain(..) { + pub fn complete_async( + &mut self, + mem: &vm_memory::GuestMemoryMmap, + ) -> result::Result<(), Error> { + for aligned_op in self.aligned_operations.drain(..) { // We need to perform the copy after the data has been read inside // the aligned buffer in case we're reading data in. if self.request_type == RequestType::In { - // SAFETY: origin buffer has been allocated with the - // proper size. - unsafe { - std::ptr::copy( - aligned_operation.aligned_ptr as *const u8, - aligned_operation.origin_ptr as *mut u8, - aligned_operation.size, - ); - }; + mem.write_slice(aligned_op.as_bytes(), aligned_op.data_addr()) + .map_err(Error::GuestMemory)?; } - - // Free the temporary aligned buffer. - // SAFETY: aligned_ptr was allocated by alloc_zeroed with the same - // layout - unsafe { - dealloc( - aligned_operation.aligned_ptr as *mut u8, - aligned_operation.layout, - ); - }; } Ok(()) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 328df0e6dd..c035d571f8 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -461,7 +461,9 @@ Setting device status to 'NEEDS_RESET' and stopping processing queues until rese let mut request = self.find_inflight_request(desc_index)?; - request.complete_async().map_err(Error::RequestCompleting)?; + request + .complete_async(&mem) + .map_err(Error::RequestCompleting)?; let latency = request.start.elapsed().as_micros() as u64; let read_ops_last = self.counters.read_ops.load(Ordering::Relaxed); From 13c55f074771d9f606c8ae9d9c6fdd322b7e3ffb Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Apr 2026 17:10:44 -0700 Subject: [PATCH 09/10] block: raw_async: reject batch atomically when SQ lacks capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit submit_batch_requests pushed each BatchRequest into the io_uring SQ in turn and used `?` to bail on the first push failure. Leaving the initial SQEs visible to the kernel — but submitter.submit() was never called, and every other call site in this file gates submit() behind a preceding sq.push() that now also fails on the full ring. This could allow a guest to DoS it's own queue or worse if the buffer is freed early. Signed-off-by: Dylan Reid (cherry picked from commit ee315d2e7c98b65a51573b3be48df9cc66115179) --- block/src/raw_async.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/src/raw_async.rs b/block/src/raw_async.rs index 539aaa9095..45fb86d1a1 100644 --- a/block/src/raw_async.rs +++ b/block/src/raw_async.rs @@ -197,6 +197,14 @@ impl AsyncIo for RawFileAsync { let (submitter, mut sq, _) = self.io_uring.split(); let mut submitted = false; + // Refuse the whole batch if it can't fit in the SQ to avoid having to unroll a partially + // successful push. + if batch_request.len() > sq.capacity() - sq.len() { + return Err(AsyncIoError::SubmitBatchRequests(Error::other( + "io_uring submission queue is full", + ))); + } + for req in batch_request { match req.request_type { RequestType::In => { From 93eee386f01c3846c6fac49e1d17d58a20597959 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Thu, 14 May 2026 19:51:48 +0000 Subject: [PATCH 10/10] build: Release v51.2 Signed-off-by: Bo Chen --- Cargo.lock | 2 +- cloud-hypervisor/Cargo.toml | 2 +- release-notes.md | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd3501a2ac..2aa24483b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,7 +512,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cloud-hypervisor" -version = "51.1.0" +version = "51.2.0" dependencies = [ "anyhow", "api_client", diff --git a/cloud-hypervisor/Cargo.toml b/cloud-hypervisor/Cargo.toml index ebb7c8d95d..39b4d1b871 100644 --- a/cloud-hypervisor/Cargo.toml +++ b/cloud-hypervisor/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor" license = "Apache-2.0 AND BSD-3-Clause" name = "cloud-hypervisor" -version = "51.1.0" +version = "51.2.0" # Minimum buildable version: # Keep in sync with version in .github/workflows/build.yaml # Policy on MSRV (see #4318): diff --git a/release-notes.md b/release-notes.md index 1f2ff74152..225039cbcf 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,4 @@ +- [v51.2](#v512) - [v51.1](#v511) - [v51.0](#v510) - [Security Fixes](#security-fixes) @@ -419,6 +420,12 @@ - [Unit testing](#unit-testing) - [Integration tests parallelization](#integration-tests-parallelization) +# v51.2 + +This is a point release containing security fixes to a use-after-free +vulnerability in the `virtio-block` async I/O completion path +(#8220). Details can be found in GHSA-f47p-p25q-83rh (CVE-2026-45782). + # v51.1 This is a bug fix release. The following issues have been addressed: