mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-virtio: Add centralized descriptor range validation
Introduce a CheckedDescriptorIter adapter over DescriptorChain that validates each descriptor's translated (addr, len) range against guest memory before yielding it. Any descriptor whose buffer is not fully backed by guest RAM is rejected, so the device never performs I/O against memory the guest does not actually own. The helper lives in vm-virtio so it can be shared across the virtio-devices and block crates, both of which already depend on vm-virtio. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
e033b593d9
commit
4c6d60f162
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2692,6 +2692,7 @@ dependencies = [
|
||||
name = "vm-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"virtio-queue",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
@@ -9,6 +9,7 @@ version = "0.1.0"
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
virtio-queue = { workspace = true }
|
||||
vm-memory = { workspace = true, features = [
|
||||
"backend-atomic",
|
||||
|
||||
178
vm-virtio/src/checked_descriptor.rs
Normal file
178
vm-virtio/src/checked_descriptor.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright © 2026 Cloud Hypervisor Authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Centralized descriptor buffer validation for virtio devices.
|
||||
//!
|
||||
//! This module provides [`CheckedDescriptorIter`], an iterator adapter over
|
||||
//! [`DescriptorChain`] that validates each descriptor's buffer range against
|
||||
//! guest memory before yielding it. Any descriptor whose translated
|
||||
//! `(addr, len)` range is not fully backed by guest RAM is rejected, so the
|
||||
//! device never performs I/O against memory the guest does not actually own.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use log::warn;
|
||||
use virtio_queue::DescriptorChain;
|
||||
use virtio_queue::desc::split::Descriptor;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
use crate::{AccessPlatform, Translatable};
|
||||
|
||||
/// A descriptor whose buffer range has been validated against guest memory.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckedDescriptor {
|
||||
inner: Descriptor,
|
||||
/// The translated guest physical address.
|
||||
addr: GuestAddress,
|
||||
}
|
||||
|
||||
impl CheckedDescriptor {
|
||||
pub fn addr(&self) -> GuestAddress {
|
||||
self.addr
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u32 {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.len() == 0
|
||||
}
|
||||
|
||||
pub fn is_write_only(&self) -> bool {
|
||||
self.inner.is_write_only()
|
||||
}
|
||||
|
||||
pub fn has_next(&self) -> bool {
|
||||
self.inner.has_next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator adapter that validates each descriptor's buffer against guest
|
||||
/// memory before yielding it.
|
||||
///
|
||||
/// Each call to [`Iterator::next`] returns `Some(Ok(desc))` for a valid
|
||||
/// descriptor, `Some(Err(addr))` when validation rejects a descriptor
|
||||
/// (with the offending guest address), or `None` when the chain is
|
||||
/// exhausted.
|
||||
pub struct CheckedDescriptorIter<'a, M> {
|
||||
chain: &'a mut DescriptorChain<M>,
|
||||
access_platform: Option<&'a dyn AccessPlatform>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl<'a, M> CheckedDescriptorIter<'a, M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
pub fn new(
|
||||
chain: &'a mut DescriptorChain<M>,
|
||||
access_platform: Option<&'a dyn AccessPlatform>,
|
||||
) -> Self {
|
||||
Self {
|
||||
chain,
|
||||
access_platform,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Iterator for CheckedDescriptorIter<'_, M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
type Item = Result<CheckedDescriptor, GuestAddress>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
|
||||
let desc = self.chain.next()?;
|
||||
|
||||
// A zero length descriptor describes no buffer, so skip
|
||||
// translation and range checks and let the device layer
|
||||
// decide. See CVE-2023-5158 for why hosts must tolerate them.
|
||||
if desc.len() == 0 {
|
||||
return Some(Ok(CheckedDescriptor {
|
||||
addr: desc.addr(),
|
||||
inner: desc,
|
||||
}));
|
||||
}
|
||||
|
||||
let desc_addr = desc.addr();
|
||||
let desc_len = desc.len() as usize;
|
||||
|
||||
let result = desc_addr
|
||||
.translate_gva(self.access_platform, desc_len)
|
||||
.map_err(|_| {
|
||||
warn!(
|
||||
"Descriptor address translation failed: addr=0x{:x} len={}",
|
||||
desc_addr.0, desc_len
|
||||
);
|
||||
desc_addr
|
||||
})
|
||||
.and_then(|addr| {
|
||||
if self.chain.memory().check_range(addr, desc_len) {
|
||||
Ok(CheckedDescriptor { inner: desc, addr })
|
||||
} else {
|
||||
warn!(
|
||||
"Descriptor buffer extends past guest memory: addr=0x{:x} len={}",
|
||||
addr.0, desc_len
|
||||
);
|
||||
Err(addr)
|
||||
}
|
||||
});
|
||||
|
||||
if result.is_err() {
|
||||
self.done = true;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait on [`DescriptorChain`] providing validated iteration.
|
||||
pub trait DescriptorChainExt<M> {
|
||||
fn checked_iter<'a>(
|
||||
&'a mut self,
|
||||
access_platform: Option<&'a dyn AccessPlatform>,
|
||||
) -> CheckedDescriptorIter<'a, M>;
|
||||
|
||||
/// Fetch the next descriptor from the chain, validating its buffer range
|
||||
/// against guest memory.
|
||||
///
|
||||
/// Returns `Ok(None)` when the chain is exhausted, and
|
||||
/// `Err(failing_addr)` when validation rejected a descriptor. Callers can
|
||||
/// map the failing address into a domain specific error.
|
||||
fn next_checked(
|
||||
&mut self,
|
||||
access_platform: Option<&dyn AccessPlatform>,
|
||||
) -> Result<Option<CheckedDescriptor>, GuestAddress>;
|
||||
}
|
||||
|
||||
impl<M> DescriptorChainExt<M> for DescriptorChain<M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
fn checked_iter<'a>(
|
||||
&'a mut self,
|
||||
access_platform: Option<&'a dyn AccessPlatform>,
|
||||
) -> CheckedDescriptorIter<'a, M> {
|
||||
CheckedDescriptorIter::new(self, access_platform)
|
||||
}
|
||||
|
||||
fn next_checked(
|
||||
&mut self,
|
||||
access_platform: Option<&dyn AccessPlatform>,
|
||||
) -> Result<Option<CheckedDescriptor>, GuestAddress> {
|
||||
match self.checked_iter(access_platform).next() {
|
||||
Some(Ok(desc)) => Ok(Some(desc)),
|
||||
Some(Err(addr)) => Err(addr),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use std::fmt::{self, Debug};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
pub mod checked_descriptor;
|
||||
pub mod queue;
|
||||
pub use queue::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user