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 <manuelh@nvidia.com>
This commit is contained in:
Manuel Huber
2026-01-27 14:41:13 -08:00
committed by Fabiano Fidêncio
parent f639c3fa17
commit 472b50fa42
8 changed files with 277 additions and 30 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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@"

View File

@@ -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 = [

View File

@@ -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(

View File

@@ -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,
));

View File

@@ -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<String> {
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<Option<KernelVerityConfig>> {
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<u64> {
match values.get(name) {
Some(value) if !value.is_empty() => value.parse::<u64>().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<String> {
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<Self> {
pub(crate) fn new_rootfs_kernel_params(
kernel_verity_params: &str,
rootfs_driver: &str,
rootfs_type: &str,
) -> Result<Self> {
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(())
}
}

View File

@@ -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(