devices: Add kernel cmdline, kernel, and initramfs to fw_cfg device

The kernel and initramfs are passed to the fw_cfg device as
file references. The cmdline is passed directly.

Signed-off-by: Alex Orozco <alexorozco@google.com>
This commit is contained in:
Alex Orozco 2025-03-24 19:50:46 +00:00 committed by Bo Chen
parent 777b7ee11e
commit 623fadfa9d
6 changed files with 203 additions and 6 deletions

4
Cargo.lock generated
View file

@ -531,6 +531,7 @@ dependencies = [
"event_monitor",
"hypervisor",
"libc",
"linux-loader",
"log",
"num_enum",
"pci",
@ -1143,8 +1144,7 @@ dependencies = [
[[package]]
name = "linux-loader"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638"
source = "git+https://github.com/rust-vmm/linux-loader?branch=main#d5f39c09d59c8f50d5313b78ce4de511b12d1848"
dependencies = [
"vm-memory",
]

View file

@ -107,7 +107,8 @@ members = [
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
kvm-bindings = "0.12.0"
kvm-ioctls = "0.22.0"
linux-loader = "0.13.0"
# TODO: update to 0.13.1+
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
mshv-bindings = "0.5.2"
mshv-ioctls = "0.5.2"
seccompiler = "0.5.0"

View file

@ -13,6 +13,11 @@ byteorder = { workspace = true }
event_monitor = { path = "../event_monitor" }
hypervisor = { path = "../hypervisor" }
libc = { workspace = true }
linux-loader = { workspace = true, features = [
"bzimage",
"elf",
"pe",
], optional = true }
log = { workspace = true }
num_enum = "0.7.2"
pci = { path = "../pci" }
@ -38,6 +43,6 @@ arch = { path = "../arch" }
[features]
default = []
fw_cfg = ["zerocopy"]
fw_cfg = ["linux-loader", "zerocopy"]
kvm = ["arch/kvm"]
pvmemcontrol = []

View file

@ -14,12 +14,17 @@
use std::{
fs::File,
io::Result,
mem::size_of_val,
mem::{size_of, size_of_val},
os::unix::fs::FileExt,
sync::{Arc, Barrier},
};
#[cfg(target_arch = "x86_64")]
use linux_loader::bootparam::boot_params;
#[cfg(target_arch = "aarch64")]
use linux_loader::loader::pe::arm64_image_header as boot_params;
use vm_device::BusDevice;
use vm_memory::ByteValued;
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
use zerocopy::{FromBytes, IntoBytes};
@ -50,6 +55,14 @@ pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
const FW_CFG_SIGNATURE: u16 = 0x00;
const FW_CFG_ID: u16 = 0x01;
const FW_CFG_KERNEL_SIZE: u16 = 0x08;
const FW_CFG_INITRD_SIZE: u16 = 0x0b;
const FW_CFG_KERNEL_DATA: u16 = 0x11;
const FW_CFG_INITRD_DATA: u16 = 0x12;
const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
const FW_CFG_CMDLINE_DATA: u16 = 0x15;
const FW_CFG_SETUP_SIZE: u16 = 0x17;
const FW_CFG_SETUP_DATA: u16 = 0x18;
const FW_CFG_FILE_DIR: u16 = 0x19;
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
@ -183,6 +196,46 @@ impl FwCfg {
Ok(())
}
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
let mut buffer = vec![0u8; size_of::<boot_params>()];
file.read_exact_at(&mut buffer, 0)?;
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
#[cfg(target_arch = "x86_64")]
{
// must set to 4 for backwards compatibility
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
if bp.hdr.setup_sects == 0 {
bp.hdr.setup_sects = 4;
}
// wildcard boot loader type
bp.hdr.type_of_loader = 0xff;
}
#[cfg(target_arch = "aarch64")]
let kernel_start = bp.text_offset;
#[cfg(target_arch = "x86_64")]
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32);
self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
self.known_items[FW_CFG_KERNEL_SIZE as usize] =
FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32);
self.known_items[FW_CFG_KERNEL_DATA as usize] =
FwCfgContent::File(kernel_start as u64, file.try_clone()?);
Ok(())
}
pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) {
let bytes = s.into_bytes_with_nul();
self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32);
self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
}
pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> {
let initramfs_size = file.metadata()?.len();
self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _);
self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?);
Ok(())
}
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
let start = offset as usize;
let end = start + size as usize;
@ -278,6 +331,11 @@ impl BusDevice for FwCfg {
#[cfg(test)]
mod tests {
use std::ffi::CString;
use std::io::Write;
use vmm_sys_util::tempfile::TempFile;
use super::*;
#[cfg(target_arch = "x86_64")]
@ -306,4 +364,51 @@ mod tests {
}
}
}
#[test]
fn test_kernel_cmdline() {
let mut fw_cfg = FwCfg::new();
let cmdline = *b"cmdline\0";
fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap());
let mut data = vec![0u8];
let mut cmdline_iter = cmdline.into_iter();
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]);
loop {
if let Some(char) = cmdline_iter.next() {
fw_cfg.read(0, DATA_OFFSET, &mut data);
assert_eq!(data[0], char);
} else {
return;
}
}
}
#[test]
fn test_initram_fs() {
let mut fw_cfg = FwCfg::new();
let temp = TempFile::new().unwrap();
let mut temp_file = temp.as_file();
let initram_content = b"this is the initramfs";
let written = temp_file.write(initram_content);
assert_eq!(written.unwrap(), 21);
let _ = fw_cfg.add_initramfs_data(temp_file);
let mut data = vec![0u8];
let mut initram_iter = (*initram_content).into_iter();
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]);
loop {
if let Some(char) = initram_iter.next() {
fw_cfg.read(0, DATA_OFFSET, &mut data);
assert_eq!(data[0], char);
} else {
return;
}
}
}
}

View file

@ -22,7 +22,12 @@ epoll = "4.3.3"
hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] }
libc = "0.2.155"
libfuzzer-sys = "0.4.7"
linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] }
# TODO: update to 0.13.1+
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main", features = [
"bzimage",
"elf",
"pe",
] }
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
mshv-bindings = "0.5.2"
net_util = { path = "../net_util" }

View file

@ -339,6 +339,18 @@ pub enum Error {
#[error("Error locking disk images: Another instance likely holds a lock")]
LockingError(#[source] DeviceManagerError),
#[cfg(feature = "fw_cfg")]
#[error("Fw Cfg missing kernel")]
MissingFwCfgKernelFile(#[source] io::Error),
#[cfg(feature = "fw_cfg")]
#[error("Fw Cfg missing initramfs")]
MissingFwCfgInitramfs(#[source] io::Error),
#[cfg(feature = "fw_cfg")]
#[error("Fw Cfg missing kernel cmdline")]
MissingFwCfgCmdline,
}
pub type Result<T> = result::Result<T, Error>;
@ -784,6 +796,72 @@ impl Vm {
})
}
#[cfg(feature = "fw_cfg")]
fn populate_fw_cfg(
device_manager: &Arc<Mutex<DeviceManager>>,
config: &Arc<Mutex<VmConfig>>,
) -> Result<()> {
let kernel = config
.lock()
.unwrap()
.payload
.as_ref()
.map(|p| p.kernel.as_ref().map(File::open))
.unwrap_or_default()
.transpose()
.map_err(Error::MissingFwCfgKernelFile)?;
if let Some(kernel_file) = kernel {
device_manager
.lock()
.unwrap()
.fw_cfg()
.expect("fw_cfg device must be present")
.lock()
.unwrap()
.add_kernel_data(&kernel_file)
.map_err(Error::MissingFwCfgKernelFile)?
}
let cmdline = Vm::generate_cmdline(
config.lock().unwrap().payload.as_ref().unwrap(),
#[cfg(target_arch = "aarch64")]
device_manager,
)
.map_err(|_| Error::MissingFwCfgCmdline)?
.as_cstring()
.map_err(|_| Error::MissingFwCfgCmdline)?;
device_manager
.lock()
.unwrap()
.fw_cfg()
.expect("fw_cfg device must be present")
.lock()
.unwrap()
.add_kernel_cmdline(cmdline);
let initramfs = config
.lock()
.unwrap()
.payload
.as_ref()
.map(|p| p.initramfs.as_ref().map(File::open))
.unwrap_or_default()
.transpose()
.map_err(Error::MissingFwCfgInitramfs)?;
// We measure the initramfs when running Oak Containers in SNP mode (initramfs = Stage1)
// o/w use Stage0 to launch cloud disk images
if let Some(initramfs_file) = initramfs {
device_manager
.lock()
.unwrap()
.fw_cfg()
.expect("fw_cfg device must be present")
.lock()
.unwrap()
.add_initramfs_data(&initramfs_file)
.map_err(Error::MissingFwCfgInitramfs)?;
}
Ok(())
}
fn create_numa_nodes(
configs: Option<Vec<NumaConfig>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
@ -2272,6 +2350,9 @@ impl Vm {
};
current_state.valid_transition(new_state)?;
#[cfg(feature = "fw_cfg")]
Self::populate_fw_cfg(&self.device_manager, &self.config)?;
// Do earlier to parallelise with loading kernel
#[cfg(target_arch = "x86_64")]
cfg_if::cfg_if! {