Skip to content

Commit 2f694f0

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 5ea493a commit 2f694f0

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};
@@ -204,16 +204,10 @@ fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
204204
}
205205

206206
/// Load linux kernel into guest memory.
207-
pub fn load_kernel(
208-
kernel: &File,
207+
pub fn load_kernel<R: ReadVolatile + Read + Seek>(
208+
mut kernel_file: R,
209209
guest_memory: &GuestMemoryMmap,
210210
) -> Result<EntryPoint, ConfigurationError> {
211-
// Need to clone the File because reading from it
212-
// mutates it.
213-
let mut kernel_file = kernel
214-
.try_clone()
215-
.map_err(|_| ConfigurationError::KernelFile)?;
216-
217211
let entry_addr = Loader::load(
218212
guest_memory,
219213
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;
@@ -447,20 +448,16 @@ fn add_e820_entry(
447448
}
448449

449450
/// Load linux kernel into guest memory.
450-
pub fn load_kernel(
451-
kernel: &File,
451+
pub fn load_kernel<R: Read + ReadVolatile + Seek>(
452+
mut kernel: R,
452453
guest_memory: &GuestMemoryMmap,
453454
) -> Result<EntryPoint, ConfigurationError> {
454455
// Need to clone the File because reading from it
455456
// mutates it.
456-
let mut kernel_file = kernel
457-
.try_clone()
458-
.map_err(|_| ConfigurationError::KernelFile)?;
459-
460457
let entry_addr = Loader::load(
461458
guest_memory,
462459
None,
463-
&mut kernel_file,
460+
&mut kernel,
464461
Some(GuestAddress(get_kernel_start())),
465462
)
466463
.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};
@@ -248,8 +251,31 @@ pub fn build_microvm_for_boot(
248251
.map_err(VmmError::Vm)?;
249252
}
250253

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

254280
#[cfg(feature = "gdb")]
255281
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

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

2629
use crate::DirtyBitmap;
@@ -53,6 +56,53 @@ pub enum MemoryError {
5356
OffsetTooLarge,
5457
}
5558

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

0 commit comments

Comments
 (0)