mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-migration: Use zerocopy for safe serialization
Getting rid of the unsafe ByteValued implementation for MemoryRange, Request and Response structures, by relying on zerocopy's safe implementation instead. Signed-off-by: Sebastien Boeuf <sboeuf@meta.com> Assisted-by: Claude:claude-opus-4-8
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2773,6 +2773,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"vm-memory",
|
"vm-memory",
|
||||||
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ serde = { workspace = true, features = ["derive", "rc"] }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
vm-memory = { workspace = true, features = ["backend-atomic", "backend-mmap"] }
|
vm-memory = { workspace = true, features = ["backend-atomic", "backend-mmap"] }
|
||||||
|
zerocopy = { workspace = true, features = ["alloc", "derive"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -88,12 +88,11 @@
|
|||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vm_memory::ByteValued;
|
use zerocopy::{FromBytes, Immutable, IntoBytes, TryFromBytes};
|
||||||
|
|
||||||
use crate::MigratableError;
|
use crate::MigratableError;
|
||||||
use crate::bitpos_iterator::BitposIteratorExt;
|
use crate::bitpos_iterator::BitposIteratorExt;
|
||||||
@@ -124,7 +123,7 @@ use crate::bitpos_iterator::BitposIteratorExt;
|
|||||||
///
|
///
|
||||||
/// [live-migration protocol]: super::protocol
|
/// [live-migration protocol]: super::protocol
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, TryFromBytes, IntoBytes, Immutable)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
#[default]
|
#[default]
|
||||||
Invalid = 0,
|
Invalid = 0,
|
||||||
@@ -197,7 +196,7 @@ pub fn supported_protocol_versions() -> RangeInclusive<u16> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy, Clone, TryFromBytes, IntoBytes, Immutable)]
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
command: Command,
|
command: Command,
|
||||||
command_headers: [u8; 6],
|
command_headers: [u8; 6],
|
||||||
@@ -205,9 +204,6 @@ pub struct Request {
|
|||||||
length: u64,
|
length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: Request contains a series of integers with no implicit padding
|
|
||||||
unsafe impl ByteValued for Request {}
|
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
fn encode_sender_version(version: u16) -> [u8; 6] {
|
fn encode_sender_version(version: u16) -> [u8; 6] {
|
||||||
let mut command_headers = [0; 6];
|
let mut command_headers = [0; 6];
|
||||||
@@ -299,21 +295,23 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
|
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
|
||||||
let mut request = Request::default();
|
let mut buf = [0u8; size_of::<Request>()];
|
||||||
fd.read_exact(Self::as_mut_slice(&mut request))
|
fd.read_exact(&mut buf)
|
||||||
.map_err(MigratableError::MigrateSocket)?;
|
.map_err(MigratableError::MigrateSocket)?;
|
||||||
|
|
||||||
Ok(request)
|
Request::try_read_from_bytes(&buf).map_err(|_| {
|
||||||
|
MigratableError::MigrateReceive(anyhow!("received request with unknown command"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||||
fd.write_all(Self::as_slice(self))
|
fd.write_all(self.as_bytes())
|
||||||
.map_err(MigratableError::MigrateSocket)
|
.map_err(MigratableError::MigrateSocket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, TryFromBytes, IntoBytes, Immutable)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
#[default]
|
#[default]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -322,16 +320,13 @@ pub enum Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy, Clone, TryFromBytes, IntoBytes, Immutable)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
status: Status,
|
status: Status,
|
||||||
padding: [u8; 6],
|
padding: [u8; 6],
|
||||||
length: u64, // Length of payload for command excluding the Response struct
|
length: u64, // Length of payload for command excluding the Response struct
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: Response contains a series of integers with no implicit padding
|
|
||||||
unsafe impl ByteValued for Response {}
|
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
pub fn new(status: Status, length: u64) -> Self {
|
pub fn new(status: Status, length: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -358,11 +353,13 @@ impl Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_from(fd: &mut dyn Read) -> Result<Response, MigratableError> {
|
pub fn read_from(fd: &mut dyn Read) -> Result<Response, MigratableError> {
|
||||||
let mut response = Response::default();
|
let mut buf = [0u8; size_of::<Response>()];
|
||||||
fd.read_exact(Self::as_mut_slice(&mut response))
|
fd.read_exact(&mut buf)
|
||||||
.map_err(MigratableError::MigrateSocket)?;
|
.map_err(MigratableError::MigrateSocket)?;
|
||||||
|
|
||||||
Ok(response)
|
Response::try_read_from_bytes(&buf).map_err(|_| {
|
||||||
|
MigratableError::MigrateReceive(anyhow!("received response with unknown status"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ok_or_abandon<T>(
|
pub fn ok_or_abandon<T>(
|
||||||
@@ -382,31 +379,40 @@ impl Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||||
fd.write_all(Self::as_slice(self))
|
fd.write_all(self.as_bytes())
|
||||||
.map_err(MigratableError::MigrateSocket)
|
.map_err(MigratableError::MigrateSocket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
FromBytes,
|
||||||
|
IntoBytes,
|
||||||
|
Immutable,
|
||||||
|
)]
|
||||||
pub struct MemoryRange {
|
pub struct MemoryRange {
|
||||||
pub gpa: u64,
|
pub gpa: u64,
|
||||||
pub length: u64,
|
pub length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: MemoryRange is two u64 fields with no padding.
|
|
||||||
unsafe impl ByteValued for MemoryRange {}
|
|
||||||
|
|
||||||
impl MemoryRange {
|
impl MemoryRange {
|
||||||
pub fn read_from(fd: &mut dyn Read) -> Result<MemoryRange, MigratableError> {
|
pub fn read_from(fd: &mut dyn Read) -> Result<MemoryRange, MigratableError> {
|
||||||
let mut range = MemoryRange::default();
|
let mut range = MemoryRange::default();
|
||||||
fd.read_exact(Self::as_mut_slice(&mut range))
|
fd.read_exact(range.as_mut_bytes())
|
||||||
.map_err(MigratableError::MigrateSocket)?;
|
.map_err(MigratableError::MigrateSocket)?;
|
||||||
Ok(range)
|
Ok(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||||
fd.write_all(Self::as_slice(self))
|
fd.write_all(self.as_bytes())
|
||||||
.map_err(MigratableError::MigrateSocket)
|
.map_err(MigratableError::MigrateSocket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,15 +567,7 @@ impl MemoryRangeTable {
|
|||||||
let mut data: Vec<MemoryRange> =
|
let mut data: Vec<MemoryRange> =
|
||||||
vec![MemoryRange::default(); length as usize / size_of::<MemoryRange>()];
|
vec![MemoryRange::default(); length as usize / size_of::<MemoryRange>()];
|
||||||
|
|
||||||
// SAFETY: The pointer points to the just created vector data.
|
fd.read_exact(data.as_mut_bytes())
|
||||||
// `MemoryRange` can be read from and written to bytes since it's `[repr(C)]`.
|
|
||||||
// The vector data was initialized with `length as usize / size_of::<MemoryRange>()` valid
|
|
||||||
// `MemoryRange`s so the memory is valid for `length` bytes.
|
|
||||||
// During the lifetime of the slice, neither the backing vector nor the pointed to memory are accessed.
|
|
||||||
let data_slice_bytes =
|
|
||||||
unsafe { slice::from_raw_parts_mut(data.as_mut_ptr().cast(), length as usize) };
|
|
||||||
|
|
||||||
fd.read_exact(data_slice_bytes)
|
|
||||||
.map_err(MigratableError::MigrateSocket)?;
|
.map_err(MigratableError::MigrateSocket)?;
|
||||||
|
|
||||||
Ok(Self { data })
|
Ok(Self { data })
|
||||||
@@ -580,11 +578,8 @@ impl MemoryRangeTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||||
// SAFETY: the slice is constructed with the correct arguments
|
fd.write_all(self.data.as_bytes())
|
||||||
fd.write_all(unsafe {
|
.map_err(MigratableError::MigrateSocket)
|
||||||
slice::from_raw_parts(self.data.as_ptr().cast(), self.length() as usize)
|
|
||||||
})
|
|
||||||
.map_err(MigratableError::MigrateSocket)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user