From 623fadfa9dd26d1b619f900f673b21ff2d112d55 Mon Sep 17 00:00:00 2001 From: Alex Orozco Date: Mon, 24 Mar 2025 19:50:46 +0000 Subject: [PATCH] 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 --- Cargo.lock | 4 +- Cargo.toml | 3 +- devices/Cargo.toml | 7 ++- devices/src/legacy/fw_cfg.rs | 107 ++++++++++++++++++++++++++++++++++- fuzz/Cargo.toml | 7 ++- vmm/src/vm.rs | 81 ++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f26735d3c..e4f4454ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 3fdfef690..89f994aaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index dc025948c..c35f58b88 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -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 = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index 8810c1435..87e4e66a3 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -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::()]; + 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 { 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; + } + } + } } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5d6ac0a29..8a7e1d484 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -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" } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 683aa3df9..f7fafeb6f 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -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 = result::Result; @@ -784,6 +796,72 @@ impl Vm { }) } + #[cfg(feature = "fw_cfg")] + fn populate_fw_cfg( + device_manager: &Arc>, + config: &Arc>, + ) -> 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>, memory_manager: &Arc>, @@ -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! {