From 89bb4b76a60c43af105d645da8b12f01ffb87d1f Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Tue, 28 Apr 2026 17:03:22 -0700 Subject: [PATCH 1/7] firmware_uefi: implement MOR (Memory Overwrite Request) bit support Implement the MOR_SET_VARIABLE (0x31) UEFI device command, which was previously defined but unhandled (falling through to 'unknown uefi write'). The MOR bit is part of the TCG Platform Reset Attack Mitigation Specification. The guest UEFI firmware uses this IO port command to persist the MemoryOverwriteRequestControl variable in NVRAM, signaling that memory should be cleared on the next boot/reset. Changes: - Define EFI_MEMORY_OVERWRITE_REQUEST_CONTROL_GUID and the MemoryOverwriteRequestControl variable in uefi_specs - Handle MOR_SET_VARIABLE in UefiDevice::write_data (persists the variable in NVRAM) and read_data (returns set status) - Add MorConfig platform trait so the hosting VMM can react to MOR being set - In Underhill, implement MorConfig by updating HvRegisterVsmPartitionConfig.zero_memory_on_reset via hypercall, ensuring the hypervisor scrubs guest memory on partition reset - Add UhPartition::set_zero_memory_on_reset and Hcl getter for the VsmPartitionConfig register to support the above - Wire mor_config=None for OpenVMM (no hypervisor interaction needed) Bug: 59084623 --- openhcl/hcl/src/ioctl/register.rs | 8 ++++ .../underhill_core/src/emuplat/firmware.rs | 30 +++++++++++++ openhcl/underhill_core/src/worker.rs | 4 ++ openhcl/virt_mshv_vtl/src/lib.rs | 22 ++++++++++ openvmm/openvmm_core/src/worker/dispatch.rs | 1 + vm/devices/firmware/firmware_uefi/src/lib.rs | 44 +++++++++++++++++++ .../firmware_uefi/src/platform/nvram.rs | 15 +++++++ .../firmware_uefi/src/service/nvram/mod.rs | 12 +++++ .../firmware/uefi_specs/src/uefi/nvram.rs | 13 ++++++ vmm_core/vmotherboard/src/base_chipset.rs | 4 ++ 10 files changed, 153 insertions(+) diff --git a/openhcl/hcl/src/ioctl/register.rs b/openhcl/hcl/src/ioctl/register.rs index ed963bb1c1..25ed37ef2e 100644 --- a/openhcl/hcl/src/ioctl/register.rs +++ b/openhcl/hcl/src/ioctl/register.rs @@ -430,6 +430,14 @@ impl Hcl { ) } + /// Get the current [`hvdef::HvRegisterVsmPartitionConfig`] register. + pub fn get_vtl2_vsm_partition_config( + &self, + ) -> Result { + let value = self.get_partition_vtl2_register(HvArchRegisterName::VsmPartitionConfig)?; + Ok(hvdef::HvRegisterVsmPartitionConfig::from(value.as_u64())) + } + /// Configure guest VSM. /// The only configuration attribute currently supported is changing the maximum number of /// guest-visible virtual trust levels for the partition. (VTL 1) diff --git a/openhcl/underhill_core/src/emuplat/firmware.rs b/openhcl/underhill_core/src/emuplat/firmware.rs index 4e94ac894d..73ab01a47b 100644 --- a/openhcl/underhill_core/src/emuplat/firmware.rs +++ b/openhcl/underhill_core/src/emuplat/firmware.rs @@ -67,3 +67,33 @@ impl firmware_uefi::platform::nvram::VsmConfig for UnderhillVsmConfig { } } } + +/// MOR (Memory Overwrite Request) configuration for Underhill. +/// +/// When the guest sets the MOR bit, this notifies the hypervisor to ensure +/// memory is scrubbed on the next partition reset by setting the +/// `zero_memory_on_reset` flag in `HvRegisterVsmPartitionConfig`. +#[derive(Debug)] +pub struct UnderhillMorConfig { + pub partition: Weak, +} + +impl firmware_uefi::platform::nvram::MorConfig for UnderhillMorConfig { + fn notify_mor_set(&self, mor_value: u8) { + const MOR_CLEAR_MEMORY_BIT_MASK: u8 = 0x01; + + let clear_memory = (mor_value & MOR_CLEAR_MEMORY_BIT_MASK) != 0; + + if clear_memory { + if let Some(partition) = self.partition.upgrade() { + if let Err(err) = partition.set_zero_memory_on_reset(true) { + tracing::warn!( + CVM_ALLOWED, + error = &err as &dyn std::error::Error, + "failed to set zero_memory_on_reset for MOR" + ); + } + } + } + } +} diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index df511c89e5..b62a842ae7 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -23,6 +23,7 @@ use crate::dispatch::vtl2_settings_worker::disk_from_disk_type; use crate::dispatch::vtl2_settings_worker::wait_for_mana; use crate::emuplat::EmuplatServicing; use crate::emuplat::firmware::UnderhillLogger; +use crate::emuplat::firmware::UnderhillMorConfig; use crate::emuplat::firmware::UnderhillVsmConfig; use crate::emuplat::framebuffer::FramebufferRemoteControl; use crate::emuplat::i440bx_host_pci_bridge::ArcMutexGetBackedAdjustGpaRange; @@ -2612,6 +2613,9 @@ async fn new_underhill_vm( partition: Arc::downgrade(&partition), })), time_source: Box::new(rtc_time_source.new_linked_clock()), + mor_config: Some(Box::new(UnderhillMorConfig { + partition: Arc::downgrade(&partition), + })), }) } FirmwareType::None => {} diff --git a/openhcl/virt_mshv_vtl/src/lib.rs b/openhcl/virt_mshv_vtl/src/lib.rs index 60ffabb807..bee44245e8 100755 --- a/openhcl/virt_mshv_vtl/src/lib.rs +++ b/openhcl/virt_mshv_vtl/src/lib.rs @@ -839,6 +839,28 @@ impl UhPartition { Ok(()) } + /// Ensures that guest memory will be zeroed on the next partition reset. + /// + /// This is used to implement TCG MOR (Memory Overwrite Request) by updating + /// `HvRegisterVsmPartitionConfig.zero_memory_on_reset`. + pub fn set_zero_memory_on_reset(&self, zero: bool) -> anyhow::Result<()> { + let mut config = self + .inner + .hcl + .get_vtl2_vsm_partition_config() + .map_err(|e| anyhow::anyhow!("failed to get VsmPartitionConfig: {e}"))?; + + config.set_zero_memory_on_reset(zero); + + self.inner + .hcl + .set_vtl2_vsm_partition_config(config) + .map_err(|e| anyhow::anyhow!("failed to set VsmPartitionConfig: {e}"))?; + + tracing::info!(zero, "updated zero_memory_on_reset for MOR"); + Ok(()) + } + /// Returns the current hypervisor reference time, in 100ns units. pub fn reference_time(&self) -> u64 { if let Some(hv) = self.inner.hv() { diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index 615740c8d1..a8c1c74e36 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -1180,6 +1180,7 @@ impl InitializedVm { time_source: Box::new(local_clock::SystemTimeClock::new( LocalClockDelta::from_millis(cfg.rtc_delta_milliseconds), )), + mor_config: None, }) } #[cfg(guest_arch = "x86_64")] diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index 78e5135487..1364f3f089 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -71,6 +71,7 @@ use inspect::InspectMut; use local_clock::InspectableLocalClock; use pal_async::local::block_on; use platform::logger::UefiLogger; +use platform::nvram::MorConfig; use platform::nvram::VsmConfig; use service::diagnostics::DEFAULT_LOGS_PER_PERIOD; use service::diagnostics::WATCHDOG_LOGS_PER_PERIOD; @@ -143,6 +144,7 @@ pub struct UefiRuntimeDeps<'a> { pub generation_id_deps: generation_id::GenerationIdRuntimeDeps, pub vsm_config: Option>, pub time_source: Box, + pub mor_config: Option>, } /// The Hyper-V UEFI services chipset device. @@ -164,6 +166,11 @@ pub struct UefiDevice { #[inspect(hex)] address: u32, + // MOR (Memory Overwrite Request) state + mor_bit_status: bool, + #[inspect(skip)] + mor_config: Option>, + // Receiver for watchdog timeout events #[inspect(skip)] watchdog_recv: mesh::Receiver<()>, @@ -185,6 +192,7 @@ impl UefiDevice { generation_id_deps, vsm_config, time_source, + mor_config, } = runtime_deps; // Create the UEFI device with the rest of the services. @@ -194,6 +202,8 @@ impl UefiDevice { address: 0, gm, watchdog_recv, + mor_bit_status: true, // initialized to success, matching legacy behavior + mor_config, service: UefiDeviceServices { nvram: service::nvram::NvramServices::new( nvram_storage, @@ -233,6 +243,10 @@ impl UefiDevice { self.handle_watchdog_read(reg) } UefiCommand::NFIT_SIZE => 0, // no NFIT + UefiCommand::MOR_SET_VARIABLE => { + // Return 1 if the last MOR SetVariable succeeded, 0 otherwise. + u32::from(self.mor_bit_status) + } _ => { tracelimit::warn_ratelimited!(?addr, "unknown uefi read"); !0 @@ -285,10 +299,38 @@ impl UefiDevice { None, ); } + UefiCommand::MOR_SET_VARIABLE => block_on(self.handle_mor_set_variable(data as u8)), _ => tracelimit::warn_ratelimited!(addr, data, "unknown uefi write"), } } + /// Handle the MOR_SET_VARIABLE command from the guest. + /// + /// Persists the MOR variable in NVRAM and notifies the platform so it can + /// arrange for memory to be scrubbed on the next reset. + async fn handle_mor_set_variable(&mut self, value: u8) { + use uefi_specs::uefi::nvram::EfiVariableAttributes; + use uefi_specs::uefi::nvram::vars; + + let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES; + + let result = self + .service + .nvram + .set_mor_variable(vars::MEMORY_OVERWRITE_REQUEST_CONTROL(), value, attr.into()) + .await; + + self.mor_bit_status = result.is_ok(); + + if self.mor_bit_status { + if let Some(mor_config) = &self.mor_config { + mor_config.notify_mor_set(value); + } + } else { + tracelimit::warn_ratelimited!("failed to set MOR variable in NVRAM"); + } + } + /// Extra inspection fields for the UEFI device. fn inspect_extra(&mut self, resp: &mut inspect::Response<'_>) { const USAGE: &str = @@ -580,6 +622,8 @@ mod save_restore { command_set: _, gm: _, watchdog_recv: _, + mor_bit_status: _, + mor_config: _, service: UefiDeviceServices { nvram, diff --git a/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs b/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs index b8f910ebf7..904120a993 100644 --- a/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs +++ b/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs @@ -16,3 +16,18 @@ pub use uefi_specs::uefi::time::EFI_TIME; pub trait VsmConfig: Send { fn revoke_guest_vsm(&self); } + +/// Callbacks for MOR (Memory Overwrite Request) bit changes. +/// +/// When the guest sets the MOR bit via the UEFI device, the platform may need +/// to take action to ensure memory is scrubbed on the next reset. In Underhill, +/// this is done by setting the `zero_memory_on_reset` flag in +/// `HvRegisterVsmPartitionConfig`. +pub trait MorConfig: Send { + /// Called when the guest sets the MOR variable. + /// + /// `mor_value` is the raw byte written by the guest. Bit 0 + /// (`MOR_CLEAR_MEMORY_BIT_MASK`) indicates whether memory should be cleared + /// on the next reset. + fn notify_mor_set(&self, mor_value: u8); +} diff --git a/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs b/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs index d08e745923..799ad50337 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs @@ -99,6 +99,18 @@ impl NvramServices { self.services.prepare_for_boot(); } + /// Set the MOR (Memory Overwrite Request) variable in NVRAM. + pub async fn set_mor_variable( + &mut self, + (vendor, name): (guid::Guid, &ucs2::Ucs2LeSlice), + value: u8, + attr: u32, + ) -> Result<(), (EfiStatus, Option)> { + self.services + .set_variable_ucs2(vendor, name, attr, vec![value]) + .await + } + /// Check if this is the VM's first boot, and if so, inject various /// hard-coded and custom UEFI vars. async fn inject_vars_on_first_boot( diff --git a/vm/devices/firmware/uefi_specs/src/uefi/nvram.rs b/vm/devices/firmware/uefi_specs/src/uefi/nvram.rs index 5556ce3909..bc08fd75d5 100644 --- a/vm/devices/firmware/uefi_specs/src/uefi/nvram.rs +++ b/vm/devices/firmware/uefi_specs/src/uefi/nvram.rs @@ -201,4 +201,17 @@ pub mod vars { defn_nvram_var!(DB = (IMAGE_SECURITY_DATABASE_GUID, "db")); defn_nvram_var!(DBX = (IMAGE_SECURITY_DATABASE_GUID, "dbx")); + + /// TCG MOR (Memory Overwrite Request) variable GUID. + /// + /// See "TCG Platform Reset Attack Mitigation Specification". + pub const EFI_MEMORY_OVERWRITE_REQUEST_CONTROL_GUID: Guid = + guid::guid!("e20939be-32d4-41be-a150-897f85d49829"); + + defn_nvram_var!( + MEMORY_OVERWRITE_REQUEST_CONTROL = ( + EFI_MEMORY_OVERWRITE_REQUEST_CONTROL_GUID, + "MemoryOverwriteRequestControl" + ) + ); } diff --git a/vmm_core/vmotherboard/src/base_chipset.rs b/vmm_core/vmotherboard/src/base_chipset.rs index 4c2d602870..c156a5f1db 100644 --- a/vmm_core/vmotherboard/src/base_chipset.rs +++ b/vmm_core/vmotherboard/src/base_chipset.rs @@ -556,6 +556,7 @@ impl<'a> BaseChipsetBuilder<'a> { watchdog_recv, vsm_config, time_source, + mor_config, }) = deps_hyperv_firmware_uefi { builder @@ -585,6 +586,7 @@ impl<'a> BaseChipsetBuilder<'a> { }, vsm_config, time_source, + mor_config, }; firmware_uefi::UefiDevice::new(runtime_deps, config, foundation.is_restoring) @@ -1387,6 +1389,8 @@ pub mod options { pub vsm_config: Option>, /// Time source pub time_source: Box, + /// Interface for MOR (Memory Overwrite Request) bit notifications. + pub mor_config: Option>, } /// Hyper-V specific framebuffer device From 2eae1ec03112a2294cc874c5266af365ee081237 Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Tue, 28 Apr 2026 17:07:35 -0700 Subject: [PATCH 2/7] firmware_uefi: add unit test for MOR variable set/get Add an async unit test that verifies the MemoryOverwriteRequestControl NVRAM variable can be set and read back through the NvramSpecServices layer, exercising the MOR GUID and variable name constants. --- .../src/service/nvram/spec_services/mod.rs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs index 2fe5df92b6..40ce1643c9 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs @@ -1955,4 +1955,45 @@ mod test { expected.extend_from_slice(&append_data); assert_eq!(data, Some(expected)); } + + #[async_test] + async fn mor_set_variable() { + use uefi_specs::uefi::nvram::vars; + + let nvram_storage = InMemoryNvram::new(); + let mut nvram = NvramSpecServices::new(nvram_storage); + + nvram.prepare_for_boot(); + + let (vendor, name) = vars::MEMORY_OVERWRITE_REQUEST_CONTROL(); + let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES; + + // Set MOR bit to 1 (request memory clear) + nvram + .set_variable_ucs2(vendor, name, attr.into(), vec![0x01]) + .await + .expect("failed to set MOR variable"); + + // Read the variable back + let NvramResult(data, status, _err) = nvram + .uefi_get_variable(Some(name.as_bytes()), vendor, &mut 0u32, &mut 256u32, false) + .await; + + assert_eq!(status, EfiStatus::SUCCESS); + assert_eq!(data, Some(vec![0x01])); + + // Clear MOR bit + nvram + .set_variable_ucs2(vendor, name, attr.into(), vec![0x00]) + .await + .expect("failed to clear MOR variable"); + + // Read back and verify cleared + let NvramResult(data, status, _err) = nvram + .uefi_get_variable(Some(name.as_bytes()), vendor, &mut 0u32, &mut 256u32, false) + .await; + + assert_eq!(status, EfiStatus::SUCCESS); + assert_eq!(data, Some(vec![0x00])); + } } From b3563ab564b401f984f8ab6f826116fdd4cd2c3a Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Thu, 30 Apr 2026 08:29:39 -0700 Subject: [PATCH 3/7] firmware_uefi: address PR review feedback - Thread 1: Log EfiStatus and NvramError on MOR set failure instead of dropping the error details - Thread 2: Include mor_bit_status in SavedState (#[mesh(8)]) so it survives save/restore and migration - Thread 3: Always write the computed clear_memory value (true/false) to the partition config, so clearing MOR also clears zero_memory_on_reset - Thread 4: Early-return in set_zero_memory_on_reset when the flag is already in the desired state (avoids redundant hypercall on guest-triggerable path), and downgrade log from info to debug --- .../underhill_core/src/emuplat/firmware.rs | 16 ++++++------- openhcl/virt_mshv_vtl/src/lib.rs | 11 ++++++--- vm/devices/firmware/firmware_uefi/src/lib.rs | 24 ++++++++++++++----- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/openhcl/underhill_core/src/emuplat/firmware.rs b/openhcl/underhill_core/src/emuplat/firmware.rs index 73ab01a47b..3f6d4df4b5 100644 --- a/openhcl/underhill_core/src/emuplat/firmware.rs +++ b/openhcl/underhill_core/src/emuplat/firmware.rs @@ -84,15 +84,13 @@ impl firmware_uefi::platform::nvram::MorConfig for UnderhillMorConfig { let clear_memory = (mor_value & MOR_CLEAR_MEMORY_BIT_MASK) != 0; - if clear_memory { - if let Some(partition) = self.partition.upgrade() { - if let Err(err) = partition.set_zero_memory_on_reset(true) { - tracing::warn!( - CVM_ALLOWED, - error = &err as &dyn std::error::Error, - "failed to set zero_memory_on_reset for MOR" - ); - } + if let Some(partition) = self.partition.upgrade() { + if let Err(err) = partition.set_zero_memory_on_reset(clear_memory) { + tracing::warn!( + CVM_ALLOWED, + error = &err as &dyn std::error::Error, + "failed to update zero_memory_on_reset for MOR" + ); } } } diff --git a/openhcl/virt_mshv_vtl/src/lib.rs b/openhcl/virt_mshv_vtl/src/lib.rs index bee44245e8..fe8e4159be 100755 --- a/openhcl/virt_mshv_vtl/src/lib.rs +++ b/openhcl/virt_mshv_vtl/src/lib.rs @@ -844,20 +844,25 @@ impl UhPartition { /// This is used to implement TCG MOR (Memory Overwrite Request) by updating /// `HvRegisterVsmPartitionConfig.zero_memory_on_reset`. pub fn set_zero_memory_on_reset(&self, zero: bool) -> anyhow::Result<()> { - let mut config = self + let config = self .inner .hcl .get_vtl2_vsm_partition_config() .map_err(|e| anyhow::anyhow!("failed to get VsmPartitionConfig: {e}"))?; - config.set_zero_memory_on_reset(zero); + // Skip the hypercall if the flag is already in the desired state. + if config.zero_memory_on_reset() == zero { + return Ok(()); + } + + let config = config.with_zero_memory_on_reset(zero); self.inner .hcl .set_vtl2_vsm_partition_config(config) .map_err(|e| anyhow::anyhow!("failed to set VsmPartitionConfig: {e}"))?; - tracing::info!(zero, "updated zero_memory_on_reset for MOR"); + tracing::debug!(zero, "updated zero_memory_on_reset for MOR"); Ok(()) } diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index 1364f3f089..d724475837 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -322,12 +322,19 @@ impl UefiDevice { self.mor_bit_status = result.is_ok(); - if self.mor_bit_status { - if let Some(mor_config) = &self.mor_config { - mor_config.notify_mor_set(value); + match result { + Ok(_) => { + if let Some(mor_config) = &self.mor_config { + mor_config.notify_mor_set(value); + } + } + Err((status, error)) => { + tracelimit::warn_ratelimited!( + ?status, + ?error, + "failed to set MOR variable in NVRAM" + ); } - } else { - tracelimit::warn_ratelimited!("failed to set MOR variable in NVRAM"); } } @@ -610,6 +617,8 @@ mod save_restore { pub time: ::SavedState, #[mesh(7)] pub diagnostics: ::SavedState, + #[mesh(8)] + pub mor_bit_status: bool, } } @@ -622,7 +631,7 @@ mod save_restore { command_set: _, gm: _, watchdog_recv: _, - mor_bit_status: _, + mor_bit_status, mor_config: _, service: UefiDeviceServices { @@ -645,6 +654,7 @@ mod save_restore { generation_id: generation_id.save()?, time: time.save()?, diagnostics: diagnostics.save()?, + mor_bit_status: *mor_bit_status, }) } @@ -658,9 +668,11 @@ mod save_restore { generation_id, time, diagnostics, + mor_bit_status, } = state; self.address = address; + self.mor_bit_status = mor_bit_status; self.service.nvram.restore(nvram)?; self.service.event_log.restore(event_log)?; From be29d70d34a0b1c14d77016cb8ac8e07cf548865 Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Fri, 1 May 2026 07:59:52 -0700 Subject: [PATCH 4/7] underhill_core: fix anyhow::Error cast in MOR notify anyhow::Error does not implement std::error::Error directly. Use err.as_ref() to get a &dyn std::error::Error for the tracing field. --- openhcl/underhill_core/src/emuplat/firmware.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhcl/underhill_core/src/emuplat/firmware.rs b/openhcl/underhill_core/src/emuplat/firmware.rs index 3f6d4df4b5..f886d7c0a7 100644 --- a/openhcl/underhill_core/src/emuplat/firmware.rs +++ b/openhcl/underhill_core/src/emuplat/firmware.rs @@ -88,7 +88,7 @@ impl firmware_uefi::platform::nvram::MorConfig for UnderhillMorConfig { if let Err(err) = partition.set_zero_memory_on_reset(clear_memory) { tracing::warn!( CVM_ALLOWED, - error = &err as &dyn std::error::Error, + error = err.as_ref() as &dyn std::error::Error, "failed to update zero_memory_on_reset for MOR" ); } From 898680dd2a6b0d00e64f870e032a01aaee52c156 Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Fri, 1 May 2026 08:34:12 -0700 Subject: [PATCH 5/7] firmware_uefi: address second round of PR feedback - Reset mor_bit_status to true on device reset (thread 5) - Use Option for SavedState mor_bit_status so older snapshots without field 8 deserialize as None -> default true (thread 6) - Use anyhow::Context instead of anyhow!() in set_zero_memory_on_reset to preserve the error chain (thread 8) --- openhcl/virt_mshv_vtl/src/lib.rs | 6 ++++-- vm/devices/firmware/firmware_uefi/src/lib.rs | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/openhcl/virt_mshv_vtl/src/lib.rs b/openhcl/virt_mshv_vtl/src/lib.rs index fe8e4159be..dfa1e90b49 100755 --- a/openhcl/virt_mshv_vtl/src/lib.rs +++ b/openhcl/virt_mshv_vtl/src/lib.rs @@ -844,11 +844,13 @@ impl UhPartition { /// This is used to implement TCG MOR (Memory Overwrite Request) by updating /// `HvRegisterVsmPartitionConfig.zero_memory_on_reset`. pub fn set_zero_memory_on_reset(&self, zero: bool) -> anyhow::Result<()> { + use anyhow::Context; + let config = self .inner .hcl .get_vtl2_vsm_partition_config() - .map_err(|e| anyhow::anyhow!("failed to get VsmPartitionConfig: {e}"))?; + .context("failed to get VsmPartitionConfig")?; // Skip the hypercall if the flag is already in the desired state. if config.zero_memory_on_reset() == zero { @@ -860,7 +862,7 @@ impl UhPartition { self.inner .hcl .set_vtl2_vsm_partition_config(config) - .map_err(|e| anyhow::anyhow!("failed to set VsmPartitionConfig: {e}"))?; + .context("failed to set VsmPartitionConfig")?; tracing::debug!(zero, "updated zero_memory_on_reset for MOR"); Ok(()) diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index d724475837..6666191999 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -399,6 +399,7 @@ impl ChangeDeviceState for UefiDevice { async fn reset(&mut self) { self.address = 0; + self.mor_bit_status = true; self.service.nvram.reset(); self.service.event_log.reset(); @@ -618,7 +619,7 @@ mod save_restore { #[mesh(7)] pub diagnostics: ::SavedState, #[mesh(8)] - pub mor_bit_status: bool, + pub mor_bit_status: Option, } } @@ -654,7 +655,7 @@ mod save_restore { generation_id: generation_id.save()?, time: time.save()?, diagnostics: diagnostics.save()?, - mor_bit_status: *mor_bit_status, + mor_bit_status: Some(*mor_bit_status), }) } @@ -672,7 +673,7 @@ mod save_restore { } = state; self.address = address; - self.mor_bit_status = mor_bit_status; + self.mor_bit_status = mor_bit_status.unwrap_or(true); self.service.nvram.restore(nvram)?; self.service.event_log.restore(event_log)?; From df6fd92c61db64eb3a194c8b2bce09249f983add Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Fri, 1 May 2026 10:10:27 -0700 Subject: [PATCH 6/7] firmware_uefi: simplify mor_bit_status saved state to plain bool Use plain bool instead of Option for SavedState.mor_bit_status. Protobuf defaults missing bool fields to false, which is the correct behavior for older snapshots that didn't track MOR status (indicating MOR support was not confirmed). --- vm/devices/firmware/firmware_uefi/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index 6666191999..20d400494a 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -619,7 +619,7 @@ mod save_restore { #[mesh(7)] pub diagnostics: ::SavedState, #[mesh(8)] - pub mor_bit_status: Option, + pub mor_bit_status: bool, } } @@ -655,7 +655,7 @@ mod save_restore { generation_id: generation_id.save()?, time: time.save()?, diagnostics: diagnostics.save()?, - mor_bit_status: Some(*mor_bit_status), + mor_bit_status: *mor_bit_status, }) } @@ -673,7 +673,7 @@ mod save_restore { } = state; self.address = address; - self.mor_bit_status = mor_bit_status.unwrap_or(true); + self.mor_bit_status = mor_bit_status; self.service.nvram.restore(nvram)?; self.service.event_log.restore(event_log)?; From e589e34a141b187fa01ea1069e3f5e2c15136087 Mon Sep 17 00:00:00 2001 From: Mike Ebersol Date: Fri, 1 May 2026 11:16:16 -0700 Subject: [PATCH 7/7] underhill_core: rate-limit MOR warning on guest-triggerable path Switch tracing::warn! to tracelimit::warn_ratelimited! in the MOR notify_mor_set callback, since this path is triggered by guest IO port writes and could spam logs. --- openhcl/underhill_core/src/emuplat/firmware.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhcl/underhill_core/src/emuplat/firmware.rs b/openhcl/underhill_core/src/emuplat/firmware.rs index f886d7c0a7..8ced753d31 100644 --- a/openhcl/underhill_core/src/emuplat/firmware.rs +++ b/openhcl/underhill_core/src/emuplat/firmware.rs @@ -86,7 +86,7 @@ impl firmware_uefi::platform::nvram::MorConfig for UnderhillMorConfig { if let Some(partition) = self.partition.upgrade() { if let Err(err) = partition.set_zero_memory_on_reset(clear_memory) { - tracing::warn!( + tracelimit::warn_ratelimited!( CVM_ALLOWED, error = err.as_ref() as &dyn std::error::Error, "failed to update zero_memory_on_reset for MOR"