diff --git a/src/runtime-rs/crates/hypervisor/src/device/driver/virtio_blk.rs b/src/runtime-rs/crates/hypervisor/src/device/driver/virtio_blk.rs index f2f9017ad5..aeed6c768f 100644 --- a/src/runtime-rs/crates/hypervisor/src/device/driver/virtio_blk.rs +++ b/src/runtime-rs/crates/hypervisor/src/device/driver/virtio_blk.rs @@ -141,6 +141,12 @@ pub struct BlockConfig { /// Physical sector size in bytes reported to the guest. 0 means use hypervisor default. pub physical_sector_size: u32, + + /// Override the QEMU virtio serial for this device. + /// When set, the device is discoverable in the guest via + /// `/dev/disk/by-id/virtio-`. + /// If empty, the default `image-{device_id}` serial is used. + pub serial_override: String, } #[derive(Debug, Clone, Default)] 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 80a9ee7b47..755554e662 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs @@ -209,6 +209,18 @@ impl Kernel { if config.disable_guest_selinux { 0 } else { 1 } ))); + // Emit one kata.extension..verity_params entry per configured + // extension. This is also the activation signal the guest-side systemd + // generator and unit key on, so it must be emitted even when + // verity_params is empty (e.g. an unmeasured extension on s390x): an + // empty value renders as a bare key, which still activates the mount. + for extra in &config.guest_extension_images { + kernel_params.append(&mut KernelParams::from_string(&format!( + "kata.extension.{}.verity_params={}", + extra.name, extra.verity_params + ))); + } + Ok(Kernel { path: config.boot_info.kernel.clone(), initrd_path: config.boot_info.initrd.clone(), @@ -1083,6 +1095,7 @@ struct DeviceVirtioBlk { share_rw: bool, discard: bool, devno: Option, + serial_override: Option, } impl DeviceVirtioBlk { @@ -1094,6 +1107,7 @@ impl DeviceVirtioBlk { share_rw: true, discard: false, devno, + serial_override: None, } } @@ -1114,6 +1128,11 @@ impl DeviceVirtioBlk { self.discard = discard; self } + + fn set_serial_override(&mut self, serial: String) -> &mut Self { + self.serial_override = Some(serial); + self + } } #[async_trait] @@ -1135,7 +1154,11 @@ impl ToQemuParams for DeviceVirtioBlk { if self.discard { params.push("discard=on".to_owned()); } - params.push(format!("serial=image-{}", self.id)); + let serial = match &self.serial_override { + Some(s) => s.clone(), + None => format!("image-{}", self.id), + }; + params.push(format!("serial={serial}")); if let Some(devno) = &self.devno { params.push(format!("devno={devno}")); } @@ -2908,6 +2931,7 @@ impl<'a> QemuCmdLine<'a> { is_direct: bool, is_scsi: bool, discard_unmap: bool, + serial_override: Option<&str>, ) -> Result<()> { let mut backend = BlockBackend::new(device_id, path, is_direct); backend.set_discard_unmap(discard_unmap); @@ -2919,6 +2943,9 @@ impl<'a> QemuCmdLine<'a> { } else { let mut device = DeviceVirtioBlk::new(device_id, bus_type(), devno); device.set_discard(discard_unmap); + if let Some(serial) = serial_override { + device.set_serial_override(serial.to_string()); + } self.devices.push(Box::new(device)); } diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs index 548b065b47..5e3dd607ee 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs @@ -141,8 +141,13 @@ impl QemuInner { &block_dev.config.path_on_host, block_dev.config.is_readonly, )?, - KATA_CCW_DEV_TYPE | KATA_BLK_DEV_TYPE | KATA_SCSI_DEV_TYPE => cmdline - .add_block_device( + KATA_CCW_DEV_TYPE | KATA_BLK_DEV_TYPE | KATA_SCSI_DEV_TYPE => { + let serial = if block_dev.config.serial_override.is_empty() { + None + } else { + Some(block_dev.config.serial_override.as_str()) + }; + cmdline.add_block_device( block_dev.device_id.as_str(), &block_dev.config.path_on_host, block_dev @@ -151,7 +156,9 @@ impl QemuInner { .unwrap_or(self.config.blockdev_info.block_device_cache_direct), block_dev.config.driver_option.as_str() == KATA_SCSI_DEV_TYPE, block_dev.config.discard_unmap, - )?, + serial, + )? + } unsupported => { info!(sl!(), "unsupported block device driver: {}", unsupported) } diff --git a/src/runtime-rs/crates/resource/src/lib.rs b/src/runtime-rs/crates/resource/src/lib.rs index a1f9d06466..7f391567fd 100644 --- a/src/runtime-rs/crates/resource/src/lib.rs +++ b/src/runtime-rs/crates/resource/src/lib.rs @@ -37,6 +37,7 @@ pub enum ResourceConfig { Network(NetworkConfig), ShareFs(SharedFsInfo), VmRootfs(BlockConfig), + GuestExtensionImage(BlockConfig), HybridVsock(HybridVsockConfig), Vsock(VsockConfig), Protection(ProtectionDeviceConfig), diff --git a/src/runtime-rs/crates/resource/src/manager_inner.rs b/src/runtime-rs/crates/resource/src/manager_inner.rs index 1122aa237f..09f40450b1 100644 --- a/src/runtime-rs/crates/resource/src/manager_inner.rs +++ b/src/runtime-rs/crates/resource/src/manager_inner.rs @@ -189,6 +189,11 @@ impl ResourceManagerInner { .await .context("do handle device failed.")?; } + ResourceConfig::GuestExtensionImage(r) => { + do_handle_device(&self.device_manager, &DeviceConfig::BlockCfg(r)) + .await + .context("do handle extra image device failed.")?; + } ResourceConfig::HybridVsock(hv) => { do_handle_device(&self.device_manager, &DeviceConfig::HybridVsockCfg(hv)) .await diff --git a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs index ef12d2e6ec..083b0f341e 100644 --- a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs +++ b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs @@ -56,6 +56,7 @@ use kata_sys_util::protection::{available_guest_protection, GuestProtection}; use kata_sys_util::spec::load_oci_spec; use kata_types::capabilities::CapabilityBits; use kata_types::config::hypervisor::Hypervisor as HypervisorConfig; +use kata_types::config::hypervisor::{VIRTIO_BLK_CCW, VIRTIO_BLK_PCI}; #[cfg(all( feature = "cloud-hypervisor", any(target_arch = "x86_64", target_arch = "aarch64") @@ -250,6 +251,15 @@ impl VirtSandbox { resource_configs.push(vm_rootfs); } + // prepare extra extension image device configs (e.g. CoCo extension) + let extra_configs = self + .prepare_guest_extension_images_config() + .await + .context("failed to prepare extra images device config")?; + for block_config in extra_configs { + resource_configs.push(ResourceConfig::GuestExtensionImage(block_config)); + } + // prepare protection device config let init_data = if let Some(initdata) = self .prepare_initdata_device_config(&self.hypervisor.hypervisor_config().await) @@ -623,6 +633,40 @@ impl VirtSandbox { })) } + async fn prepare_guest_extension_images_config(&self) -> Result> { + let hv_config = self.hypervisor.hypervisor_config().await; + let mut configs = Vec::new(); + + // Extension images must be cold-plugged as virtio-blk, because the + // guest discovers each extension by its deterministic serial + // (extension-), and only virtio-blk devices carry that serial. + // We therefore always enforce a virtio-blk transport here (the + // architecture's virtio-blk-ccw on s390x, virtio-blk-pci elsewhere) + // rather than reusing vm_rootfs_driver or block_device_driver: those + // may resolve to a non-virtio-blk transport such as virtio-pmem + // (NVDIMM, no serial) or virtio-scsi, which would leave the extension + // undiscoverable and its mount unit would fail closed. + let block_driver = if uses_native_ccw_bus() { + VIRTIO_BLK_CCW.to_string() + } else { + VIRTIO_BLK_PCI.to_string() + }; + for extra in &hv_config.guest_extension_images { + if extra.path.is_empty() { + continue; + } + configs.push(BlockConfig { + path_on_host: extra.path.clone(), + is_readonly: true, + driver_option: block_driver.clone(), + serial_override: format!("extension-{}", extra.name), + ..Default::default() + }); + } + + Ok(configs) + } + async fn set_agent_policy(&self) -> Result<()> { // TODO: Exclude policy-related items from the annotations. let toml_config = self.resource_manager.config().await;