From 472b50fa4211aeea85f0890a7e8564acda51ee40 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Tue, 27 Jan 2026 14:41:13 -0800 Subject: [PATCH] runtime-rs: Enable kernelinit dm-verity variant This change introduces the kernel_verity_parameters knob to the rust based shim, picking up dm-verity information in a new config field (the corresponding build variable is already produced by the shim build). The change extends the shim to parse dm-verity information from this parameter and to construct the kernel command line appropriately, based on the indicated initramfs or kernelinit build variant. Signed-off-by: Manuel Huber --- .../kata-types/src/config/hypervisor/mod.rs | 4 + src/runtime-rs/Makefile | 4 +- .../configuration-qemu-tdx-runtime-rs.toml.in | 5 + .../hypervisor/src/ch/inner_hypervisor.rs | 9 +- .../crates/hypervisor/src/dragonball/inner.rs | 9 +- .../hypervisor/src/firecracker/fc_api.rs | 10 +- .../crates/hypervisor/src/kernel_param.rs | 249 +++++++++++++++++- .../hypervisor/src/qemu/cmdline_generator.rs | 17 +- 8 files changed, 277 insertions(+), 30 deletions(-) diff --git a/src/libs/kata-types/src/config/hypervisor/mod.rs b/src/libs/kata-types/src/config/hypervisor/mod.rs index 536465b812..a8ba4b6a06 100644 --- a/src/libs/kata-types/src/config/hypervisor/mod.rs +++ b/src/libs/kata-types/src/config/hypervisor/mod.rs @@ -294,6 +294,10 @@ pub struct BootInfo { #[serde(default)] pub kernel_params: String, + /// Guest kernel dm-verity parameters. + #[serde(default)] + pub kernel_verity_params: String, + /// Path to initrd file on host. #[serde(default)] pub initrd: String, diff --git a/src/runtime-rs/Makefile b/src/runtime-rs/Makefile index 15b0a95404..95350455ff 100644 --- a/src/runtime-rs/Makefile +++ b/src/runtime-rs/Makefile @@ -148,8 +148,7 @@ ifneq (,$(QEMUCMD)) endif endif -ROOTMEASURECONFIG ?= "" -KERNELTDXPARAMS += $(ROOTMEASURECONFIG) +KERNELVERITYPARAMS ?= "" # TDX DEFSHAREDFS_QEMU_TDX_VIRTIOFS := none @@ -527,6 +526,7 @@ USER_VARS += MACHINEACCELERATORS USER_VARS += CPUFEATURES USER_VARS += DEFMACHINETYPE_CLH USER_VARS += KERNELPARAMS +USER_VARS += KERNELVERITYPARAMS USER_VARS += KERNELPARAMS_DB USER_VARS += KERNELPARAMS_FC USER_VARS += LIBEXECDIR diff --git a/src/runtime-rs/config/configuration-qemu-tdx-runtime-rs.toml.in b/src/runtime-rs/config/configuration-qemu-tdx-runtime-rs.toml.in index 5582b7413e..abb0499fab 100644 --- a/src/runtime-rs/config/configuration-qemu-tdx-runtime-rs.toml.in +++ b/src/runtime-rs/config/configuration-qemu-tdx-runtime-rs.toml.in @@ -74,6 +74,11 @@ valid_hypervisor_paths = @QEMUVALIDHYPERVISORPATHS@ # container and look for 'default-kernel-parameters' log entries. kernel_params = "@KERNELTDXPARAMS@" +# Optional dm-verity parameters (comma-separated key=value list): +# mode=kernelinit|initramfs,root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=... +# These are used by the runtime to assemble dm-verity kernel params. +kernel_verity_params = "@KERNELVERITYPARAMS@" + # Path to the firmware. # If you want that qemu uses the default firmware leave this option empty firmware = "@FIRMWARETDXPATH@" diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs index 82ff896e79..f2debb5cc1 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -151,7 +151,11 @@ impl CloudHypervisorInner { #[cfg(target_arch = "aarch64")] let console_param_debug = KernelParams::from_string("console=ttyAMA0,115200n8"); - let mut rootfs_param = KernelParams::new_rootfs_kernel_params(rootfs_driver, rootfs_type)?; + let mut rootfs_params = KernelParams::new_rootfs_kernel_params( + &cfg.boot_info.kernel_verity_params, + rootfs_driver, + rootfs_type, + )?; let mut console_params = if enable_debug { if confidential_guest { @@ -165,8 +169,7 @@ impl CloudHypervisorInner { params.append(&mut console_params); - // Add the rootfs device - params.append(&mut rootfs_param); + params.append(&mut rootfs_params); // Now add some additional options required for CH let extra_options = [ diff --git a/src/runtime-rs/crates/hypervisor/src/dragonball/inner.rs b/src/runtime-rs/crates/hypervisor/src/dragonball/inner.rs index 638835c098..d810d9e07f 100644 --- a/src/runtime-rs/crates/hypervisor/src/dragonball/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/dragonball/inner.rs @@ -144,13 +144,14 @@ impl DragonballInner { let mut kernel_params = KernelParams::new(self.config.debug_info.enable_debug); if self.config.boot_info.initrd.is_empty() { - // get rootfs driver + // When booting from the image, add rootfs and verity parameters here. let rootfs_driver = self.config.blockdev_info.block_device_driver.clone(); - - kernel_params.append(&mut KernelParams::new_rootfs_kernel_params( + let mut rootfs_params = KernelParams::new_rootfs_kernel_params( + &self.config.boot_info.kernel_verity_params, &rootfs_driver, &self.config.boot_info.rootfs_type, - )?); + )?; + kernel_params.append(&mut rootfs_params); } kernel_params.append(&mut KernelParams::from_string( diff --git a/src/runtime-rs/crates/hypervisor/src/firecracker/fc_api.rs b/src/runtime-rs/crates/hypervisor/src/firecracker/fc_api.rs index 732c67885f..8fe9de784a 100644 --- a/src/runtime-rs/crates/hypervisor/src/firecracker/fc_api.rs +++ b/src/runtime-rs/crates/hypervisor/src/firecracker/fc_api.rs @@ -86,12 +86,12 @@ impl FcInner { let mut kernel_params = KernelParams::new(self.config.debug_info.enable_debug); kernel_params.push(Param::new("pci", "off")); kernel_params.push(Param::new("iommu", "off")); - let rootfs_driver = self.config.blockdev_info.block_device_driver.clone(); - - kernel_params.append(&mut KernelParams::new_rootfs_kernel_params( - &rootfs_driver, + let mut rootfs_params = KernelParams::new_rootfs_kernel_params( + &self.config.boot_info.kernel_verity_params, + &self.config.blockdev_info.block_device_driver, &self.config.boot_info.rootfs_type, - )?); + )?; + kernel_params.append(&mut rootfs_params); kernel_params.append(&mut KernelParams::from_string( &self.config.boot_info.kernel_params, )); diff --git a/src/runtime-rs/crates/hypervisor/src/kernel_param.rs b/src/runtime-rs/crates/hypervisor/src/kernel_param.rs index d899e4c184..65064794b1 100644 --- a/src/runtime-rs/crates/hypervisor/src/kernel_param.rs +++ b/src/runtime-rs/crates/hypervisor/src/kernel_param.rs @@ -14,13 +14,136 @@ use kata_types::config::LOG_VPORT_OPTION; use kata_types::fs::{ VM_ROOTFS_FILESYSTEM_EROFS, VM_ROOTFS_FILESYSTEM_EXT4, VM_ROOTFS_FILESYSTEM_XFS, }; +use std::collections::HashMap; // Port where the agent will send the logs. Logs are sent through the vsock in cases // where the hypervisor has no console.sock, i.e dragonball const VSOCK_LOGS_PORT: &str = "1025"; const KERNEL_KV_DELIMITER: &str = "="; -const KERNEL_PARAM_DELIMITER: &str = " "; +const KERNEL_PARAM_DELIMITER: char = ' '; +const VERITY_BLOCK_SIZE_BYTES: u64 = 512; + +// Split kernel params on spaces, but keep quoted substrings intact. +// Example: dm-mod.create="dm-verity,,,ro,0 736328 verity 1 /dev/vda1 /dev/vda2 ...". +fn split_kernel_params(params_string: &str) -> Vec { + let mut params = Vec::new(); + let mut current = String::new(); + let mut in_quote = false; + + for c in params_string.chars() { + if c == '"' { + in_quote = !in_quote; + current.push(c); + continue; + } + + if c == KERNEL_PARAM_DELIMITER && !in_quote { + let trimmed = current.trim(); + if !trimmed.is_empty() { + params.push(trimmed.to_string()); + } + current.clear(); + continue; + } + + current.push(c); + } + + let trimmed = current.trim(); + if !trimmed.is_empty() { + params.push(trimmed.to_string()); + } + + params +} + +struct KernelVerityConfig { + mode: String, + root_hash: String, + salt: String, + data_blocks: u64, + data_block_size: u64, + hash_block_size: u64, +} + +fn parse_kernel_verity_params(params_string: &str) -> Result> { + if params_string.trim().is_empty() { + return Ok(None); + } + + let mut values = HashMap::new(); + for field in params_string.split(',') { + let field = field.trim(); + if field.is_empty() { + continue; + } + let mut parts = field.splitn(2, '='); + let key = parts.next().unwrap_or(""); + let value = parts.next().ok_or_else(|| anyhow!("Invalid kernel_verity_params entry: {field}"))?; + values.insert(key.to_string(), value.to_string()); + } + + let mode = values + .get("mode") + .ok_or_else(|| anyhow!("Missing kernel_verity_params mode"))? + .to_string(); + + if mode != VERITY_MODE_KERNELINIT && mode != VERITY_MODE_INITRAMFS { + return Err(anyhow!("Invalid kernel_verity_params mode: {mode}")); + } + + let root_hash = values + .get("root_hash") + .ok_or_else(|| anyhow!("Missing kernel_verity_params root_hash"))? + .to_string(); + + let salt = values.get("salt").cloned().unwrap_or_default(); + let (data_blocks, data_block_size, hash_block_size) = if mode == VERITY_MODE_KERNELINIT { + let parse_uint_field = |name: &str| -> Result { + match values.get(name) { + Some(value) if !value.is_empty() => value.parse::().map_err(|e| { + anyhow!("Invalid kernel_verity_params {} '{}': {}", name, value, e) + }), + _ => Err(anyhow!("Missing kernel_verity_params {}", name)), + } + }; + + ( + parse_uint_field("data_blocks")?, + parse_uint_field("data_block_size")?, + parse_uint_field("hash_block_size")?, + ) + } else { + (0, 0, 0) + }; + + Ok(Some(KernelVerityConfig { + mode, + root_hash, + salt, + data_blocks, + data_block_size, + hash_block_size, + })) +} + +fn kernel_verity_root_flags(rootfs_type: &str) -> Result { + let normalized = if rootfs_type.is_empty() { + VM_ROOTFS_FILESYSTEM_EXT4 + } else { + rootfs_type + }; + + match normalized { + VM_ROOTFS_FILESYSTEM_EXT4 => Ok("data=ordered,errors=remount-ro ro".to_string()), + VM_ROOTFS_FILESYSTEM_XFS | VM_ROOTFS_FILESYSTEM_EROFS => Ok("ro".to_string()), + _ => Err(anyhow!("Unsupported rootfs type {}", rootfs_type)), + } +} + +const VERITY_MODE_KERNELINIT: &str = "kernelinit"; +const VERITY_MODE_INITRAMFS: &str = "initramfs"; #[derive(Debug, Clone, PartialEq)] pub struct Param { @@ -71,7 +194,11 @@ impl KernelParams { Self { params } } - pub(crate) fn new_rootfs_kernel_params(rootfs_driver: &str, rootfs_type: &str) -> Result { + pub(crate) fn new_rootfs_kernel_params( + kernel_verity_params: &str, + rootfs_driver: &str, + rootfs_type: &str, + ) -> Result { let mut params = vec![]; match rootfs_driver { @@ -119,7 +246,81 @@ impl KernelParams { params.push(Param::new("rootfstype", rootfs_type)); - Ok(Self { params }) + let mut params = Self { params }; + let cfg = match parse_kernel_verity_params(kernel_verity_params)? { + Some(cfg) => cfg, + None => return Ok(params), + }; + + if cfg.mode == VERITY_MODE_INITRAMFS { + params.append(&mut KernelParams::from_string(&format!( + "rootfs_verity.scheme=dm-verity rootfs_verity.hash={}", + cfg.root_hash + ))); + return Ok(params); + } + + if cfg.salt.is_empty() { + return Err(anyhow!("Missing kernel_verity_params salt")); + } + if cfg.data_blocks == 0 || cfg.data_block_size == 0 || cfg.hash_block_size == 0 { + return Err(anyhow!( + "Invalid kernel_verity_params data_blocks/data_block_size/hash_block_size: must be non-zero" + )); + } + if cfg.data_block_size % VERITY_BLOCK_SIZE_BYTES != 0 { + return Err(anyhow!( + "Invalid kernel_verity_params data_block_size: must be multiple of {}", + VERITY_BLOCK_SIZE_BYTES + )); + } + if cfg.hash_block_size % VERITY_BLOCK_SIZE_BYTES != 0 { + return Err(anyhow!( + "Invalid kernel_verity_params hash_block_size: must be multiple of {}", + VERITY_BLOCK_SIZE_BYTES + )); + } + + let (root_device, hash_device) = match rootfs_driver { + VM_ROOTFS_DRIVER_PMEM => ("/dev/pmem0p1", "/dev/pmem0p2"), + VM_ROOTFS_DRIVER_BLK | VM_ROOTFS_DRIVER_BLK_CCW | VM_ROOTFS_DRIVER_MMIO => { + ("/dev/vda1", "/dev/vda2") + } + _ => return Err(anyhow!("Unsupported rootfs driver {}", rootfs_driver)), + }; + + let data_sectors = (cfg.data_block_size / VERITY_BLOCK_SIZE_BYTES) * cfg.data_blocks; + let root_flags = kernel_verity_root_flags(rootfs_type)?; + + let dm_cmd = format!( + "dm-verity,,,ro,0 {} verity 1 {} {} {} {} {} 0 sha256 {} {}", + data_sectors, + root_device, + hash_device, + cfg.data_block_size, + cfg.hash_block_size, + cfg.data_blocks, + cfg.root_hash, + cfg.salt + ); + + params.remove_all_by_key("root".to_string()); + params.remove_all_by_key("rootflags".to_string()); + params.remove_all_by_key("rootfstype".to_string()); + + params.push(Param { + key: "dm-mod.create".to_string(), + value: format!("\"{}\"", dm_cmd), + }); + params.push(Param::new("root", "/dev/dm-0")); + params.push(Param::new("rootflags", &root_flags)); + if rootfs_type.is_empty() { + params.push(Param::new("rootfstype", VM_ROOTFS_FILESYSTEM_EXT4)); + } else { + params.push(Param::new("rootfstype", rootfs_type)); + } + + Ok(params) } pub(crate) fn append(&mut self, params: &mut KernelParams) { @@ -138,7 +339,7 @@ impl KernelParams { pub(crate) fn from_string(params_string: &str) -> Self { let mut params = vec![]; - let parameters_vec: Vec<&str> = params_string.split(KERNEL_PARAM_DELIMITER).collect(); + let parameters_vec = split_kernel_params(params_string); for param in parameters_vec.iter() { if param.is_empty() { @@ -170,7 +371,7 @@ impl KernelParams { parameters.push(param.to_string()?); } - Ok(parameters.join(KERNEL_PARAM_DELIMITER)) + Ok(parameters.join(&KERNEL_PARAM_DELIMITER.to_string())) } } @@ -347,7 +548,8 @@ mod tests { for (i, t) in tests.iter().enumerate() { let msg = format!("test[{i}]: {t:?}"); - let result = KernelParams::new_rootfs_kernel_params(t.rootfs_driver, t.rootfs_type); + let result = + KernelParams::new_rootfs_kernel_params("", t.rootfs_driver, t.rootfs_type); let msg = format!("{msg}, result: {result:?}"); if t.result.is_ok() { assert!(result.is_ok(), "{}", msg); @@ -359,4 +561,39 @@ mod tests { } } } + + #[test] + fn test_kernel_verity_params() -> Result<()> { + let params = KernelParams::new_rootfs_kernel_params( + "mode=initramfs,root_hash=abc", + VM_ROOTFS_DRIVER_PMEM, + VM_ROOTFS_FILESYSTEM_EXT4, + )?; + assert!(params + .to_string()? + .contains("rootfs_verity.scheme=dm-verity")); + assert!(params.to_string()?.contains("rootfs_verity.hash=abc")); + assert!(params.to_string()?.contains("root=")); + + let params = KernelParams::new_rootfs_kernel_params( + "mode=kernelinit,root_hash=abc,salt=def,data_blocks=1,data_block_size=4096,hash_block_size=4096", + VM_ROOTFS_DRIVER_BLK, + VM_ROOTFS_FILESYSTEM_EXT4, + )?; + let params_string = params.to_string()?; + assert!(params_string.contains("dm-mod.create=")); + assert!(params_string.contains("root=/dev/dm-0")); + assert!(params_string.contains("rootfstype=ext4")); + + let err = KernelParams::new_rootfs_kernel_params( + "mode=kernelinit,root_hash=abc,data_blocks=1,data_block_size=4096,hash_block_size=4096", + VM_ROOTFS_DRIVER_BLK, + VM_ROOTFS_FILESYSTEM_EXT4, + ) + .err() + .expect("expected missing salt error"); + assert!(format!("{err}").contains("Missing kernel_verity_params salt")); + + Ok(()) + } } diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs index 959439daf4..31886b1988 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs @@ -179,16 +179,13 @@ impl Kernel { let mut kernel_params = KernelParams::new(config.debug_info.enable_debug); if config.boot_info.initrd.is_empty() { - // QemuConfig::validate() has already made sure that if initrd is - // empty, image cannot be so we don't need to re-check that here - - kernel_params.append( - &mut KernelParams::new_rootfs_kernel_params( - &config.boot_info.vm_rootfs_driver, - &config.boot_info.rootfs_type, - ) - .context("adding rootfs params failed")?, - ); + let mut rootfs_params = KernelParams::new_rootfs_kernel_params( + &config.boot_info.kernel_verity_params, + &config.boot_info.vm_rootfs_driver, + &config.boot_info.rootfs_type, + ) + .context("adding rootfs/verity params failed")?; + kernel_params.append(&mut rootfs_params); } kernel_params.append(&mut KernelParams::from_string(