Skip to content

Commit 03606fc

Browse files
committed
Add helper for Read/Write through bounce buffers
When guest memory is backed by direct map removed guest_memfd ("secret free"), we cannot load the guest kernel / initrd by read-ing into guest memory, as the kernel won't be able to access guest memory. Instead we'll need to read into a userspace buffer, and then memcpy into guest memory. Add a newtype that wraps Read/Write and performs exactly this operation, and use it to load guest kernel / initrd. Signed-off-by: Patrick Roy <roypat@amazon.co.uk>
1 parent 5c752f1 commit 03606fc

File tree

5 files changed

+99
-50
lines changed

5 files changed

+99
-50
lines changed

src/vmm/src/arch/aarch64/mod.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ pub mod vm;
1818

1919
use std::cmp::min;
2020
use std::fmt::Debug;
21-
use std::fs::File;
21+
use std::io::{Read, Seek};
2222

2323
use linux_loader::loader::pe::PE as Loader;
2424
use linux_loader::loader::{Cmdline, KernelLoader};
25-
use vm_memory::GuestMemoryError;
25+
use vm_memory::{GuestMemoryError, ReadVolatile};
2626

2727
use crate::arch::{BootProtocol, EntryPoint};
2828
use crate::cpu_config::aarch64::{CpuConfiguration, CpuConfigurationError};
@@ -179,16 +179,10 @@ fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
179179
}
180180

181181
/// Load linux kernel into guest memory.
182-
pub fn load_kernel(
183-
kernel: &File,
182+
pub fn load_kernel<R: ReadVolatile + Read + Seek>(
183+
mut kernel_file: R,
184184
guest_memory: &GuestMemoryMmap,
185185
) -> Result<EntryPoint, ConfigurationError> {
186-
// Need to clone the File because reading from it
187-
// mutates it.
188-
let mut kernel_file = kernel
189-
.try_clone()
190-
.map_err(|_| ConfigurationError::KernelFile)?;
191-
192186
let entry_addr = Loader::load(
193187
guest_memory,
194188
Some(GuestAddress(get_kernel_start())),

src/vmm/src/arch/x86_64/mod.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod xstate;
3131
#[allow(missing_docs)]
3232
pub mod generated;
3333

34-
use std::fs::File;
34+
use std::io::{Read, Seek};
3535

3636
use layout::CMDLINE_START;
3737
use linux_loader::configurator::linux::LinuxBootConfigurator;
@@ -44,6 +44,7 @@ use linux_loader::loader::elf::start_info::{
4444
};
4545
use linux_loader::loader::{Cmdline, KernelLoader, PvhBootCapability, load_cmdline};
4646
use log::debug;
47+
use vm_memory::ReadVolatile;
4748

4849
use super::EntryPoint;
4950
use crate::acpi::create_acpi_tables;
@@ -446,20 +447,16 @@ fn add_e820_entry(
446447
}
447448

448449
/// Load linux kernel into guest memory.
449-
pub fn load_kernel(
450-
kernel: &File,
450+
pub fn load_kernel<R: Read + ReadVolatile + Seek>(
451+
mut kernel: R,
451452
guest_memory: &GuestMemoryMmap,
452453
) -> Result<EntryPoint, ConfigurationError> {
453454
// Need to clone the File because reading from it
454455
// mutates it.
455-
let mut kernel_file = kernel
456-
.try_clone()
457-
.map_err(|_| ConfigurationError::KernelFile)?;
458-
459456
let entry_addr = Loader::load(
460457
guest_memory,
461458
None,
462-
&mut kernel_file,
459+
&mut kernel,
463460
Some(GuestAddress(get_kernel_start())),
464461
)
465462
.map_err(ConfigurationError::KernelLoader)?;

src/vmm/src/builder.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use std::fmt::Debug;
77
use std::io::{self};
8+
use std::os::unix::fs::MetadataExt;
89
#[cfg(feature = "gdb")]
910
use std::sync::mpsc;
1011
use std::sync::{Arc, Mutex};
@@ -57,10 +58,12 @@ use crate::persist::{
5758
use crate::resources::VmResources;
5859
use crate::seccomp::BpfThreadMap;
5960
use crate::snapshot::Persist;
61+
use crate::utils::u64_to_usize;
6062
use crate::vmm_config::instance_info::InstanceInfo;
6163
use crate::vmm_config::machine_config::MachineConfigError;
6264
use crate::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
6365
use crate::vstate::kvm::Kvm;
66+
use crate::vstate::memory::Bounce;
6467
use crate::vstate::vcpu::{Vcpu, VcpuError};
6568
use crate::vstate::vm::Vm;
6669
use crate::{EventManager, Vmm, VmmError, device_manager};
@@ -250,8 +253,31 @@ pub fn build_microvm_for_boot(
250253
.map_err(VmmError::Vm)?;
251254
}
252255

253-
let entry_point = load_kernel(&boot_config.kernel_file, vmm.vm.guest_memory())?;
254-
let initrd = InitrdConfig::from_config(boot_config, vmm.vm.guest_memory())?;
256+
let entry_point = load_kernel(
257+
Bounce::new(
258+
&boot_config.kernel_file,
259+
vm_resources.machine_config.mem_config.secret_free,
260+
),
261+
vmm.vm.guest_memory(),
262+
)?;
263+
let initrd = match &boot_config.initrd_file {
264+
Some(initrd_file) => {
265+
let size = initrd_file
266+
.metadata()
267+
.map_err(InitrdError::Metadata)?
268+
.size();
269+
270+
Some(InitrdConfig::from_reader(
271+
vmm.vm.guest_memory(),
272+
Bounce::new(
273+
initrd_file,
274+
vm_resources.machine_config.mem_config.secret_free,
275+
),
276+
u64_to_usize(size),
277+
)?)
278+
}
279+
None => None,
280+
};
255281

256282
#[cfg(feature = "gdb")]
257283
let (gdb_tx, gdb_rx) = mpsc::channel();

src/vmm/src/initrd.rs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::fs::File;
5-
use std::os::unix::fs::MetadataExt;
6-
74
use vm_memory::{GuestAddress, GuestMemory, ReadVolatile, VolatileMemoryError};
85

96
use crate::arch::initrd_load_addr;
10-
use crate::utils::u64_to_usize;
11-
use crate::vmm_config::boot_source::BootConfig;
127
use crate::vstate::memory::GuestMemoryMmap;
138

149
/// Errors associated with initrd loading.
@@ -20,8 +15,6 @@ pub enum InitrdError {
2015
Load,
2116
/// Cannot image metadata: {0}
2217
Metadata(std::io::Error),
23-
/// Cannot copy initrd file fd: {0}
24-
CloneFd(std::io::Error),
2518
/// Cannot load initrd due to an invalid image: {0}
2619
Read(VolatileMemoryError),
2720
}
@@ -36,31 +29,20 @@ pub struct InitrdConfig {
3629
}
3730

3831
impl InitrdConfig {
39-
/// Load initrd into guest memory based on the boot config.
40-
pub fn from_config(
41-
boot_cfg: &BootConfig,
42-
vm_memory: &GuestMemoryMmap,
43-
) -> Result<Option<Self>, InitrdError> {
44-
Ok(match &boot_cfg.initrd_file {
45-
Some(f) => {
46-
let f = f.try_clone().map_err(InitrdError::CloneFd)?;
47-
Some(Self::from_file(vm_memory, f)?)
48-
}
49-
None => None,
50-
})
51-
}
52-
5332
/// Loads the initrd from a file into guest memory.
54-
pub fn from_file(vm_memory: &GuestMemoryMmap, mut file: File) -> Result<Self, InitrdError> {
55-
let size = file.metadata().map_err(InitrdError::Metadata)?.size();
56-
let size = u64_to_usize(size);
33+
pub fn from_reader<R: ReadVolatile>(
34+
vm_memory: &GuestMemoryMmap,
35+
mut reader: R,
36+
size: usize,
37+
) -> Result<Self, InitrdError> {
5738
let Some(address) = initrd_load_addr(vm_memory, size) else {
5839
return Err(InitrdError::Address);
5940
};
6041
let mut slice = vm_memory
6142
.get_slice(GuestAddress(address), size)
6243
.map_err(|_| InitrdError::Load)?;
63-
file.read_exact_volatile(&mut slice)
44+
reader
45+
.read_exact_volatile(&mut slice)
6446
.map_err(InitrdError::Read)?;
6547

6648
Ok(InitrdConfig {
@@ -105,7 +87,7 @@ mod tests {
10587

10688
// Need to reset the cursor to read initrd properly.
10789
tempfile.seek(SeekFrom::Start(0)).unwrap();
108-
let initrd = InitrdConfig::from_file(&gm, tempfile).unwrap();
90+
let initrd = InitrdConfig::from_reader(&gm, tempfile, image.len()).unwrap();
10991
assert!(gm.address_in_range(initrd.address));
11092
assert_eq!(initrd.size, image.len());
11193
}
@@ -120,7 +102,7 @@ mod tests {
120102

121103
// Need to reset the cursor to read initrd properly.
122104
tempfile.seek(SeekFrom::Start(0)).unwrap();
123-
let res = InitrdConfig::from_file(&gm, tempfile);
105+
let res = InitrdConfig::from_reader(&gm, tempfile, image.len());
124106
assert!(matches!(res, Err(InitrdError::Address)), "{:?}", res);
125107
}
126108

@@ -134,7 +116,7 @@ mod tests {
134116

135117
// Need to reset the cursor to read initrd properly.
136118
tempfile.seek(SeekFrom::Start(0)).unwrap();
137-
let res = InitrdConfig::from_file(&gm, tempfile);
119+
let res = InitrdConfig::from_reader(&gm, tempfile, image.len());
138120
assert!(matches!(res, Err(InitrdError::Address)), "{:?}", res);
139121
}
140122
}

src/vmm/src/vstate/memory.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// found in the THIRD-PARTY file.
77

88
use std::fs::File;
9-
use std::io::SeekFrom;
9+
use std::io::{Read, Seek, SeekFrom};
1010
use std::mem::ManuallyDrop;
1111
use std::sync::Arc;
1212

@@ -21,7 +21,10 @@ pub use vm_memory::{
2121
Address, ByteValued, Bytes, FileOffset, GuestAddress, GuestMemory, GuestMemoryRegion,
2222
GuestUsize, MemoryRegionAddress, MmapRegion, address,
2323
};
24-
use vm_memory::{Error as VmMemoryError, GuestMemoryError, VolatileSlice, WriteVolatile};
24+
use vm_memory::{
25+
Error as VmMemoryError, GuestMemoryError, ReadVolatile, VolatileMemoryError, VolatileSlice,
26+
WriteVolatile,
27+
};
2528
use vmm_sys_util::errno;
2629

2730
use crate::DirtyBitmap;
@@ -54,6 +57,53 @@ pub enum MemoryError {
5457
OffsetTooLarge,
5558
}
5659

60+
/// Newtype that implements [`ReadVolatile`] and [`WriteVolatile`] if `T` implements `Read` or
61+
/// `Write` respectively, by reading/writing using a bounce buffer, and memcpy-ing into the
62+
/// [`VolatileSlice`].
63+
#[derive(Debug)]
64+
pub struct Bounce<T>(pub T, pub bool);
65+
66+
impl Bounce<File> {
67+
/// Create a new [`Bounce`] by cloning the given file.
68+
///
69+
/// Needed because `ReadVolatile` is not implemented for &File, so we need to either
70+
/// dup the file, or get a mutable reference from somewhere.
71+
pub fn new(file: &File, do_bounce: bool) -> Self {
72+
Self(file.try_clone().expect("dup to succeed"), do_bounce)
73+
}
74+
}
75+
76+
impl<T: Read + ReadVolatile> ReadVolatile for Bounce<T> {
77+
fn read_volatile<B: BitmapSlice>(
78+
&mut self,
79+
buf: &mut VolatileSlice<B>,
80+
) -> Result<usize, VolatileMemoryError> {
81+
if self.1 {
82+
let mut bbuf = vec![0; buf.len()];
83+
let n = self
84+
.0
85+
.read(bbuf.as_mut_slice())
86+
.map_err(VolatileMemoryError::IOError)?;
87+
buf.copy_from(&bbuf[..n]);
88+
Ok(n)
89+
} else {
90+
self.0.read_volatile(buf)
91+
}
92+
}
93+
}
94+
95+
impl<R: Read> Read for Bounce<R> {
96+
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
97+
self.0.read(buf)
98+
}
99+
}
100+
101+
impl<S: Seek> Seek for Bounce<S> {
102+
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
103+
self.0.seek(pos)
104+
}
105+
}
106+
57107
/// A memory region, described in terms of `kvm_userspace_memory_region`
58108
#[derive(Debug)]
59109
pub struct KvmRegion {

0 commit comments

Comments
 (0)