mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-07-14 04:51:43 +00:00
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:
committed by
Fabiano Fidêncio
parent
f639c3fa17
commit
472b50fa42
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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@"
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user