From 0e291f9c914cce35921c06e92c29276e3633f319 Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Mon, 27 Apr 2026 12:40:04 +0200 Subject: [PATCH 01/21] fix(attestation): fix UB in FFI return type for servtd_attest functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C library libservtd_attest.a uses servtd_attest_error_t values (e.g. SERVTD_ATTEST_ERROR_QUOTE_FAILURE = 0x0070), but the Rust FFI declarations used the AttestLibError enum whose discriminants match the internal TDX_ATTEST_ERROR_* range (e.g. QuoteFailure = 0x0008). When the C code returned a value outside the Rust enum's discriminant set, this created an invalid enum value — undefined behavior in Rust. In Release builds the UB was silent, but in Debug builds (Rust 1.88 with ub_checks enabled) it triggered a panic: "unsafe precondition(s) violated: hint::unreachable_unchecked must never be reached" Fix by changing FFI return types from AttestLibError to i32 and comparing against 0 (success) instead of AttestLibError::Success. This matches the C API contract where 0 means success and any non-zero value is an error code. Error codes are now logged in hex for easier cross-reference with the C header. Affected functions: get_quote, verify_quote_integrity, verify_quote_integrity_ex, init_heap. Signed-off-by: Michał Tarnacki --- src/attestation/src/attest.rs | 14 +++++++------- src/attestation/src/binding.rs | 27 ++++++++++++--------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/attestation/src/attest.rs b/src/attestation/src/attest.rs index 656b85c5f..38e841a20 100644 --- a/src/attestation/src/attest.rs +++ b/src/attestation/src/attest.rs @@ -102,10 +102,10 @@ pub fn get_quote(td_report: &[u8]) -> Result, Error> { quote.as_mut_ptr() as *mut c_void, &mut quote_size as *mut u32, ); - if result != AttestLibError::Success { - log::error!("get_quote_inner failed with error: {:?}\n", result); + if result != 0 { + log::error!("get_quote_inner failed with error: {:#x}\n", result); return Err(match result { - AttestLibError::Busy => Error::Busy, + x if x == AttestLibError::Busy as i32 => Error::Busy, _ => Error::GetQuote, }); } @@ -132,8 +132,8 @@ pub fn verify_quote(quote: &[u8]) -> Result, Error> { td_report_verify.as_mut_ptr() as *mut c_void, &mut report_verify_size as *mut u32, ); - if result != AttestLibError::Success { - log::error!("verify_quote_integrity failed with error: {:?}\n", result); + if result != 0 { + log::error!("verify_quote_integrity failed with error: {:#x}\n", result); return Err(Error::VerifyQuote); } } @@ -174,9 +174,9 @@ pub fn verify_quote_with_collaterals( td_report_verify.as_mut_ptr() as *mut c_void, &mut report_verify_size as *mut u32, ); - if result != AttestLibError::Success { + if result != 0 { log::error!( - "verify_quote_integrity_ex failed with error: {:?}\n", + "verify_quote_integrity_ex failed with error: {:#x}\n", result ); return Err(Error::VerifyQuote); diff --git a/src/attestation/src/binding.rs b/src/attestation/src/binding.rs index 5f68eea1b..018113b8d 100644 --- a/src/attestation/src/binding.rs +++ b/src/attestation/src/binding.rs @@ -84,7 +84,7 @@ mod attest_lib_binding { tdx_report_size: u32, p_quote: *mut ::core::ffi::c_void, p_quote_size: *mut u32, - ) -> AttestLibError; + ) -> i32; /// Verify the integrity of MigTD's Quote and return td report of MigTD /// Note: all IN/OUT memory should be managed by Caller @@ -106,7 +106,7 @@ mod attest_lib_binding { root_pub_key_size: u32, p_tdx_report_verify: *mut ::core::ffi::c_void, p_tdx_report_verify_size: *mut u32, - ) -> AttestLibError; + ) -> i32; #[cfg(feature = "attest-lib-ext")] pub fn verify_quote_integrity_ex( @@ -117,7 +117,7 @@ mod attest_lib_binding { p_collateral: *const QveCollateral, p_tdx_report_verify: *mut ::core::ffi::c_void, p_tdx_report_verify_size: *mut u32, - ) -> AttestLibError; + ) -> i32; /// Allocate heap space for MigTD Attestation library internal use, /// Must be called only once by MigTD before other attestation lib APIs @@ -127,10 +127,7 @@ mod attest_lib_binding { /// /// @return true: Successfully init heap for internal use. /// @return false: Failed to init heap for internal use. E.g. the parameter is incorrect, etc. - pub fn init_heap( - p_td_heap_base: *const ::core::ffi::c_void, - td_heap_size: u32, - ) -> AttestLibError; + pub fn init_heap(p_td_heap_base: *const ::core::ffi::c_void, td_heap_size: u32) -> i32; } } @@ -145,9 +142,9 @@ mod null_binding { _tdx_report_size: u32, _p_quote: *mut ::core::ffi::c_void, _p_quote_size: *mut u32, - ) -> AttestLibError { + ) -> i32 { *_p_quote_size = TD_VERIFIED_REPORT_SIZE as u32; - AttestLibError::Success + 0 } #[no_mangle] @@ -158,8 +155,8 @@ mod null_binding { _root_pub_key_size: u32, _p_tdx_report_verify: *mut ::core::ffi::c_void, _p_tdx_report_verify_size: *mut u32, - ) -> AttestLibError { - AttestLibError::Success + ) -> i32 { + 0 } #[cfg(feature = "attest-lib-ext")] @@ -172,15 +169,15 @@ mod null_binding { p_collateral: *const QveCollateral, p_tdx_report_verify: *mut ::core::ffi::c_void, p_tdx_report_verify_size: *mut u32, - ) -> AttestLibError { - AttestLibError::Success + ) -> i32 { + 0 } #[no_mangle] pub unsafe extern "C" fn init_heap( _p_td_heap_base: *const ::core::ffi::c_void, _td_heap_size: u32, - ) -> AttestLibError { - AttestLibError::Success + ) -> i32 { + 0 } } From fdf9d77728a229fca9b7612847b62bbbac5b7192 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:29:15 +0000 Subject: [PATCH 02/21] feat(migtd): Use panic_with_guest_crash_reg_report() Also replaced hard-coded 0xFF error code with proper MigrationResult variants Signed-off-by: Haitao Huang --- src/migtd/src/bin/migtd/cvmemu.rs | 6 +- src/migtd/src/bin/migtd/main.rs | 91 ++++++++++++++++++++++++------ src/migtd/src/driver/timer.rs | 11 +++- src/migtd/src/driver/vmcall_raw.rs | 2 +- src/migtd/src/migration/event.rs | 3 +- src/migtd/src/migration/mod.rs | 2 + 6 files changed, 92 insertions(+), 23 deletions(-) diff --git a/src/migtd/src/bin/migtd/cvmemu.rs b/src/migtd/src/bin/migtd/cvmemu.rs index 16ade0117..475c34a8a 100644 --- a/src/migtd/src/bin/migtd/cvmemu.rs +++ b/src/migtd/src/bin/migtd/cvmemu.rs @@ -12,6 +12,7 @@ use std::process; use alloc::vec::Vec; use migtd; +use migtd::driver::vmcall_raw::panic_with_guest_crash_reg_report; use migtd::migration::event; use migtd::migration::logging::{ create_logarea, enable_logarea, init_vmm_logger, u8_to_levelfilter, @@ -31,7 +32,10 @@ pub fn main() { // Initialize VMM logger (outputs to console via td-logger-emu in AzCVMEmu mode) let result = init_vmm_logger(); if result.is_err() { - panic!("Failed to initialize VMM logger"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to initialize VMM logger", + ); } // Init internal heap diff --git a/src/migtd/src/bin/migtd/main.rs b/src/migtd/src/bin/migtd/main.rs index 637a0bd0c..b856c6453 100644 --- a/src/migtd/src/bin/migtd/main.rs +++ b/src/migtd/src/bin/migtd/main.rs @@ -16,6 +16,7 @@ use alloc::vec::Vec; use log::info; #[cfg(feature = "vmcall-raw")] use log::{debug, Level}; +use migtd::driver::vmcall_raw::panic_with_guest_crash_reg_report; use migtd::event_log::*; #[cfg(not(feature = "vmcall-raw"))] use migtd::migration::data::MigrationInformation; @@ -109,7 +110,10 @@ pub fn runtime_main() { { let result = init_vmm_logger(); if result.is_err() { - panic!("Failed to initialize VMM logger"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to initialize VMM logger", + ); } let _ = create_logarea(); } @@ -126,7 +130,10 @@ pub fn runtime_main() { #[cfg(not(feature = "vmcall-raw"))] { if query().is_err() { - panic!("Migration is not supported by VMM"); + panic_with_guest_crash_reg_report( + MigrationResult::Unsupported as u64, + b"Migration is not supported by VMM", + ); } } @@ -155,7 +162,10 @@ fn do_measurements() { log::error!( "Failed to get the event log - firmware did not allocate event log buffer\n" ); - panic!("Failed to get the event log"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to get the event log", + ); } }; @@ -180,7 +190,10 @@ fn do_measurements() { log::error!( "Failed to get the event log - firmware did not allocate event log buffer\n" ); - panic!("Failed to get the event log"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to get the event log", + ); } }; @@ -206,7 +219,10 @@ fn measure_test_feature(event_log: &mut [u8]) { ) .map_err(|e| { log::error!("Failed to log migtd test feature: {:?}\n", e); - panic!("Failed to log migtd test feature"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to log migtd test feature", + ); }); } @@ -217,7 +233,10 @@ fn get_policy_and_measure(event_log: &mut [u8]) { Some(policy) => policy, None => { log::error!("Fail to get policy from CFV\n"); - panic!("Fail to get policy from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get policy from CFV", + ); } }; @@ -233,7 +252,10 @@ fn get_policy_and_measure(event_log: &mut [u8]) { ) .map_err(|e| { log::error!("Failed to log migration policy: {:?}\n", e); - panic!("Failed to log migration policy"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to log migration policy", + ); }); } @@ -244,7 +266,10 @@ fn get_policy_and_measure(event_log: &mut [u8]) { Some(policy) => policy, None => { log::error!("Fail to get policy from CFV\n"); - panic!("Fail to get policy from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get policy from CFV", + ); } }; @@ -262,7 +287,10 @@ fn get_policy_and_measure(event_log: &mut [u8]) { ) .map_err(|e| { log::error!("Failed to log migration policy: {:?}\n", e); - panic!("Failed to log migration policy"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to log migration policy", + ); }); } @@ -273,7 +301,10 @@ fn get_policy_issuer_chain_and_measure(event_log: &mut [u8]) { Some(policy_issuer_chain) => policy_issuer_chain, None => { log::error!("Fail to get policy issuer chain from CFV\n"); - panic!("Fail to get policy issuer chain from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get policy issuer chain from CFV", + ); } }; @@ -287,7 +318,10 @@ fn get_policy_issuer_chain_and_measure(event_log: &mut [u8]) { ) .map_err(|e| { log::error!("Failed to log policy issuer chain: {:?}\n", e); - panic!("Failed to log policy issuer chain"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to log policy issuer chain", + ); }); } @@ -297,7 +331,10 @@ fn get_ca_and_measure(event_log: &mut [u8]) { Some(policy_issuer_chain) => policy_issuer_chain, None => { log::error!("Fail to get root certificate chain from CFV\n"); - panic!("Fail to get root certificate chain from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get root certificate chain from CFV", + ); } }; @@ -311,14 +348,20 @@ fn get_ca_and_measure(event_log: &mut [u8]) { ) .map_err(|e| { log::error!("Failed to log SGX root CA: {:?}\n", e); - panic!("Failed to log SGX root CA"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to log SGX root CA", + ); }); match attestation::root_ca::set_ca(root_ca) { Ok(_) => (), Err(e) => { log::error!("Invalid root certificate: {:?}\n", e); - panic!("Invalid root certificate"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Invalid root certificate", + ); } } } @@ -331,26 +374,38 @@ fn initialize_policy() -> String { Some(policy) => policy, None => { log::error!("Fail to get policy from CFV\n"); - panic!("Fail to get policy from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get policy from CFV", + ); } }; let policy_issuer_chain = match config::get_policy_issuer_chain() { Some(chain) => chain, None => { log::error!("Fail to get policy issuer chain from CFV\n"); - panic!("Fail to get policy issuer chain from CFV"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Fail to get policy issuer chain from CFV", + ); } }; // Initialize and verify the migration policy let version = mig_policy::init_policy(policy, policy_issuer_chain).map_err(|e| { log::error!("Failed to initialize migration policy: {:?}\n", e); - panic!("Failed to initialize migration policy"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Failed to initialize migration policy", + ); }); // Initialize and verify the migration policy let _ = mig_policy::init_tcb_info().map_err(|e| { log::error!("Failed to initialize migration TCB info: {:?}\n", e); - panic!("Failed to initialize migration TCB info"); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"Failed to initialize migration TCB info", + ); }); version.expect("Failed to initialize migration policy") diff --git a/src/migtd/src/driver/timer.rs b/src/migtd/src/driver/timer.rs index 230103304..856f92665 100644 --- a/src/migtd/src/driver/timer.rs +++ b/src/migtd/src/driver/timer.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent use super::vmcall_raw::panic_with_guest_crash_reg_report; +use crate::migration::MigrationResult; use core::sync::atomic::{AtomicBool, Ordering}; use spin::Once; use td_payload::arch::apic::*; @@ -42,7 +43,10 @@ pub fn init_timer() { TSC_DEADLINE_ENABLED.store(false, Ordering::SeqCst); #[cfg(not(feature = "oneshot-apic"))] { - panic!("Please enable TSC deadline mode for TD"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Please enable TSC deadline mode for TD", + ); } } @@ -122,7 +126,10 @@ fn set_timer_notification(vector: u8) { // Setup interrupt handler if register_interrupt_callback(vector as usize, InterruptCallback::new(timer_handler)).is_err() { - panic_with_guest_crash_reg_report(0xFF, b"Failed to set interrupt callback for timer"); + panic_with_guest_crash_reg_report( + MigrationResult::InitializationError as u64, + b"Failed to set interrupt callback for timer", + ); } } diff --git a/src/migtd/src/driver/vmcall_raw.rs b/src/migtd/src/driver/vmcall_raw.rs index 739492e0b..3a013d0ac 100644 --- a/src/migtd/src/driver/vmcall_raw.rs +++ b/src/migtd/src/driver/vmcall_raw.rs @@ -15,7 +15,7 @@ pub fn vmcall_raw_device_init() { } #[track_caller] -pub fn panic_with_guest_crash_reg_report(errorcode: u64, msg: &[u8]) { +pub fn panic_with_guest_crash_reg_report(errorcode: u64, msg: &[u8]) -> ! { #[cfg(not(feature = "vmcall-raw"))] let _ = errorcode; let location = core::panic::Location::caller(); diff --git a/src/migtd/src/migration/event.rs b/src/migtd/src/migration/event.rs index d8e5cda3b..3bbd64958 100644 --- a/src/migtd/src/migration/event.rs +++ b/src/migtd/src/migration/event.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent use crate::driver::vmcall_raw::panic_with_guest_crash_reg_report; +use crate::migration::MigrationResult; use core::sync::atomic::{AtomicBool, Ordering}; use td_payload::arch::apic::*; use td_payload::arch::idt::{register_interrupt_callback, InterruptCallback, InterruptStack}; @@ -22,7 +23,7 @@ pub fn register_callback() { .is_err() { panic_with_guest_crash_reg_report( - 0xFF, + MigrationResult::InitializationError as u64, b"Failed to set interrupt callback for VMCALL_SERVICE", ); } diff --git a/src/migtd/src/migration/mod.rs b/src/migtd/src/migration/mod.rs index 8afe5a6ab..d800b4ac0 100644 --- a/src/migtd/src/migration/mod.rs +++ b/src/migtd/src/migration/mod.rs @@ -265,6 +265,7 @@ pub enum MigrationResult { VmmCanceled = 10, VmmInternalError = 11, UnsupportedOperationError = 12, + InitializationError = 0xFF, } impl TryFrom for MigrationResult { @@ -285,6 +286,7 @@ impl TryFrom for MigrationResult { 10 => Ok(MigrationResult::VmmCanceled), 11 => Ok(MigrationResult::VmmInternalError), 12 => Ok(MigrationResult::UnsupportedOperationError), + 0xFF => Ok(MigrationResult::InitializationError), _ => Err(()), } } From 30e49c9f52821986d928afbdfd57d36d9f5531ff Mon Sep 17 00:00:00 2001 From: olivia-wu-epsf <237100685+olivia-wu-epsf@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:20:38 -0700 Subject: [PATCH 03/21] Fix VirtIO descriptor length validation bugs Clamp VMM-controlled descriptor len values to the known DMA allocation size before passing them to slice_from_raw_parts/from_raw_parts. This prevents out-of-bounds reads when a malicious VMM overwrites the shared DMA descriptor table with inflated length values. Vsock driver (src/devices/vsock/src/transport/virtio_pci.rs): - pkt[1].len is now clamped to dma_record.dma_size in the receive path - Changed contains_key to get to retrieve the DMA record for size validation VirtIO serial driver (src/devices/virtio_serial/src/lib.rs): - Control path: Added DMA address validation via dma_allocation.get() (previously missing) and clamped vq_buf.len to record.dma_size - Data path: Changed contains_key to get, clamped buffer.len to record.dma_size, and clamped the downstream used_len to safe_len to prevent a secondary OOB on the slice index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/devices/virtio_serial/src/lib.rs | 26 ++++++++++--------- src/devices/vsock/src/transport/virtio_pci.rs | 7 +++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/devices/virtio_serial/src/lib.rs b/src/devices/virtio_serial/src/lib.rs index f75e52772..0f133c598 100644 --- a/src/devices/virtio_serial/src/lib.rs +++ b/src/devices/virtio_serial/src/lib.rs @@ -497,14 +497,16 @@ impl VirtioSerial { .pop_used(&mut g2h, &mut h2g)?; for vq_buf in &h2g { - let control_msg = unsafe { - core::slice::from_raw_parts(vq_buf.addr as *const u8, vq_buf.len as usize) - }; + if let Some(record) = self.dma_allocation.get(&vq_buf.addr) { + let safe_len = core::cmp::min(vq_buf.len as usize, record.dma_size); + let control_msg = + unsafe { core::slice::from_raw_parts(vq_buf.addr as *const u8, safe_len) }; - self.handle_control_msg(control_msg)?; + self.handle_control_msg(control_msg)?; - self.free_dma_memory(vq_buf.addr) - .ok_or(VirtioSerialError::OutOfResource)?; + self.free_dma_memory(vq_buf.addr) + .ok_or(VirtioSerialError::OutOfResource)?; + } } } @@ -766,15 +768,15 @@ impl VirtioSerial { if remaining == 0 { return Err(VirtioSerialError::InvalidParameter); } - if self.dma_allocation.contains_key(&buffer.addr) { - let dma_buf = - unsafe { from_raw_parts(buffer.addr as *const u8, buffer.len as usize) }; + if let Some(record) = self.dma_allocation.get(&buffer.addr) { + let safe_len = core::cmp::min(buffer.len as usize, record.dma_size); + let dma_buf = unsafe { from_raw_parts(buffer.addr as *const u8, safe_len) }; let mut data_buf = Vec::new(); - let used_len = core::cmp::min(len, buffer.len); - data_buf.extend_from_slice(&dma_buf[..used_len as usize]); + let used_len = core::cmp::min(core::cmp::min(len, buffer.len) as usize, safe_len); + data_buf.extend_from_slice(&dma_buf[..used_len]); remaining = remaining - .checked_sub(used_len) + .checked_sub(used_len as u32) .ok_or(VirtioSerialError::InvalidParameter)?; Self::port_queue_push(port_id, data_buf)?; diff --git a/src/devices/vsock/src/transport/virtio_pci.rs b/src/devices/vsock/src/transport/virtio_pci.rs index c9492ccc3..9caf4c92e 100644 --- a/src/devices/vsock/src/transport/virtio_pci.rs +++ b/src/devices/vsock/src/transport/virtio_pci.rs @@ -242,10 +242,9 @@ impl VirtioVsock { return Err(VsockTransportError::InvalidVsockPacket); } - if self.dma_record.contains_key(&pkt[1].addr) { - let dma_buf = unsafe { - &*slice_from_raw_parts(pkt[1].addr as *const u8, pkt[1].len as usize) - }; + if let Some(record) = self.dma_record.get(&pkt[1].addr) { + let safe_len = core::cmp::min(pkt[1].len as usize, record.dma_size); + let dma_buf = unsafe { &*slice_from_raw_parts(pkt[1].addr as *const u8, safe_len) }; data_buf.extend_from_slice(dma_buf); } From 87c0dff11e97c55bd07ea78988d2fca4753636d9 Mon Sep 17 00:00:00 2001 From: Haitao Huang Date: Mon, 20 Apr 2026 23:05:07 +0000 Subject: [PATCH 04/21] fix(migtd): Remove cached LOCAL_TCB_INFO Currently, migtd requests a quote and cache a static tcb during MigTD startup. This adds extra latency for migtd to start and the cached tcb would become invalid after an impactless update for FW. The only benefit of doing it is to verify the policy and fails fast if the policy is malformed. That can be done by build pipelines offline. Remove the LOCAL_TCB_INFO static cache and init_tcb_info() function. get_local_tcb_evaluation_info() now generates a fresh quote and computes evaluation data on every call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Haitao Huang --- src/migtd/src/bin/migtd/main.rs | 9 --------- src/migtd/src/mig_policy.rs | 33 +++++++++------------------------ 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/src/migtd/src/bin/migtd/main.rs b/src/migtd/src/bin/migtd/main.rs index b856c6453..040e2b138 100644 --- a/src/migtd/src/bin/migtd/main.rs +++ b/src/migtd/src/bin/migtd/main.rs @@ -399,15 +399,6 @@ fn initialize_policy() -> String { ); }); - // Initialize and verify the migration policy - let _ = mig_policy::init_tcb_info().map_err(|e| { - log::error!("Failed to initialize migration TCB info: {:?}\n", e); - panic_with_guest_crash_reg_report( - MigrationResult::InvalidPolicyError as u64, - b"Failed to initialize migration TCB info", - ); - }); - version.expect("Failed to initialize migration policy") } diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index b055cd429..194366f71 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -90,7 +90,6 @@ mod v2 { const SERVTD_TYPE_MIGTD: u16 = 0; lazy_static! { - pub static ref LOCAL_TCB_INFO: Once = Once::new(); pub static ref VERIFIED_POLICY: Once> = Once::new(); } @@ -113,30 +112,16 @@ mod v2 { .map(|p| p.get_version().to_string()) } - /// Initialize the global local TCB info once - pub fn init_tcb_info() -> Result<(), PolicyError> { - // Store in the global static - LOCAL_TCB_INFO - .try_call_once(|| { - let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; - let (quote, _report) = - crate::quote::get_quote_with_retry(&[0u8; 64]).map_err(|err| match err { - crate::quote::QuoteError::ReportGenerationFailed => { - PolicyError::GetTdxReport - } - _ => PolicyError::QuoteGeneration, - })?; - let (fmspc, suppl_data) = verify_quote("e, policy.get_collaterals())?; - setup_evaluation_data(fmspc, &suppl_data, policy, policy.get_collaterals()) - }) - .map(|_| ()) - } - + /// Generate a fresh local TCB evaluation info on demand by creating a + /// quote and verifying it against the policy collaterals. pub fn get_local_tcb_evaluation_info() -> Result { - LOCAL_TCB_INFO - .get() - .cloned() - .ok_or(PolicyError::InvalidParameter) + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + let tdx_report = tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) + .map_err(|_| PolicyError::GetTdxReport)?; + let quote = attestation::get_quote(tdx_report.as_bytes()) + .map_err(|_| PolicyError::QuoteGeneration)?; + let (fmspc, suppl_data) = verify_quote("e, policy.get_collaterals())?; + setup_evaluation_data(fmspc, &suppl_data, policy, policy.get_collaterals()) } /// Get reference to the global verified policy From c4ad047dbfaed0aa9eb374a38a6c5c73cadf6510 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 23:59:27 +0000 Subject: [PATCH 05/21] build(deps): bump tokio in /deps/td-shim-AzCVMEmu/azcvm-extract-report Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.1 to 1.52.2. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.1...tokio-1.52.2) --- updated-dependencies: - dependency-name: tokio dependency-version: 1.52.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock index 41ad07941..ee5ac7d8f 100644 --- a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock +++ b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock @@ -1309,9 +1309,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", From ae5862167164f429e5b6acee9439febf4c145820 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 19:55:14 +0000 Subject: [PATCH 06/21] build(deps): bump ubuntu from 24.04 to 26.04 in /container Bumps ubuntu from 24.04 to 26.04. --- updated-dependencies: - dependency-name: ubuntu dependency-version: '26.04' dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- container/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/Dockerfile b/container/Dockerfile index e4609413a..2f89ab013 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:24.04@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b +FROM ubuntu:26.04@sha256:f3d28607ddd78734bb7f71f117f3c6706c666b8b76cbff7c9ff6e5718d46ff64 # Adding rust binaries to PATH. ENV PATH="$PATH:/root/.cargo/bin" From d4cf9e5410f667663241fe53eeec31b25520c9d7 Mon Sep 17 00:00:00 2001 From: Haitao Huang Date: Sun, 12 Apr 2026 20:23:49 +0000 Subject: [PATCH 07/21] feat(migtd): add use-mock-quote build feature Add a use-mock-quote build feature that lets MigTD run on a real TDX platform but bypass the GetQuote vmcall path to the host, returning a canned mock quote instead. This is useful for testing and development when the host infrastructure does not yet fully support real quote generation. Code changes: - attestation: add use-mock-quote feature with mock TD report and quote generation, mutually exclusive with igvm-attest (compile_error guard) - attestation/tdreport: new wrapper module that overrides tdcall_report() to return a mock TdxReport when use-mock-quote is enabled, and re-exports the rest of tdx-tdcall's tdreport API unchanged - migtd: add use-mock-quote Cargo feature plumbing through to attestation - event_log: bypass RTMR verification in use-mock-quote mode (mock quote cannot match the live event log) - ratls: bypass public-key-hash check in TD report under use-mock-quote - spdm: add use-mock-quote cfg guards in verify_report_data_binding and in v1/v2 peer attestation REPORTDATA binding checks Build and test infrastructure: - Azure/Makefile: add build-igvm-mock-quote and build-igvm-mock-quote-allow-all build targets and supporting generate-policy-mock-quote* recipes - build_azure_mock_test.sh: end-to-end policy generation from mock data with auto-generated default templates when not present Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Haitao Huang --- sh_script/Azure/build_azure_mock_test.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sh_script/Azure/build_azure_mock_test.sh b/sh_script/Azure/build_azure_mock_test.sh index 611c609be..f15e02a19 100755 --- a/sh_script/Azure/build_azure_mock_test.sh +++ b/sh_script/Azure/build_azure_mock_test.sh @@ -285,6 +285,18 @@ else ACTIVE_POLICY_DATA_RAW="$POLICY_DATA_RAW" fi +# Generate default template files if they don't exist (these are overwritten by the build) +if [ ! -f "$TD_IDENTITY_TEMPLATE" ]; then + echo -e "${YELLOW}Generating default td_identity.json template${NC}" + printf '{"id":"A0998F0F-B2F3-4872-8138-FBC2B853E8C6","version":1,"issueDate":"2025-01-01T00:00:00Z","nextUpdate":"2026-01-01T00:00:00Z","tcbEvaluationNumber":1,"xfam":"0000000000000000","attributes":"0000000000000000","mrConfigId":"%s","mrOwner":"%s","mrOwnerConfig":"%s","mrsigner":"%s","isvProdId":0,"tcbLevels":[{"tcb":{"isvsvn":1},"tcbDate":"2025-01-01T00:00:00Z","tcbStatus":"UpToDate"}]}' \ + "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" > "$TD_IDENTITY_TEMPLATE" +fi +if [ ! -f "$TCB_MAPPING_TEMPLATE" ]; then + echo -e "${YELLOW}Generating default tcb_mapping.json template${NC}" + printf '{"id":"BB9668CA-4EE8-4523-941A-B3B03BE46E03","version":1,"issueDate":"2025-01-01T00:00:00Z","nextUpdate":"2026-01-01T00:00:00Z","mrSigner":"%s","isvProdId":1,"svnMappings":[{"tdMeasurements":{"mrtd":"%s","rtmr0":"%s","rtmr1":"%s"},"isvsvn":1}]}' \ + "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" "$(printf '0%.0s' {1..96})" > "$TCB_MAPPING_TEMPLATE" +fi + # Verify input files exist for file in "$ACTIVE_POLICY_DATA_RAW" "$TD_IDENTITY_TEMPLATE" "$TCB_MAPPING_TEMPLATE"; do if [ ! -f "$file" ]; then From 16f656863fe40301cd1c3cc6b46b65bcf9f266ae Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Wed, 6 May 2026 10:03:01 +0200 Subject: [PATCH 08/21] fix(Cargo.lock): resolve dependabot alerts #13-19 in openssl and rand packages This commit updates openssl and rand packages to resolve dependabot alerts Signed-off-by: Stanislaw Grams --- Cargo.lock | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 466c055bc..43d00cb77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1536,15 +1536,14 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" dependencies = [ "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1571,9 +1570,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" dependencies = [ "cc", "libc", @@ -1797,9 +1796,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core 0.9.3", From 59e43ed30464123b6f2f5e1f65ab95d0dc6e8868 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 19:57:05 +0000 Subject: [PATCH 09/21] build(deps): bump tokio in /deps/td-shim-AzCVMEmu/azcvm-extract-report Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.2 to 1.52.3. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.2...tokio-1.52.3) --- updated-dependencies: - dependency-name: tokio dependency-version: 1.52.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock index ee5ac7d8f..1ac8873a5 100644 --- a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock +++ b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock @@ -1309,9 +1309,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.2" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", From 893bb86d59c17c0b8fa6ab5d4bdc8381a1a238d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 19:55:06 +0000 Subject: [PATCH 10/21] build(deps): bump github/codeql-action from 4.35.2 to 4.35.4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.2 to 4.35.4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/95e58e9a2cdfd71adc6e0353d5c52f41a045d225...68bde559dea0fdcac2102bfdf6230c5f70eb485e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/format.yml | 2 +- .github/workflows/oss-fuzz.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/trivy.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9d0983a96..559ebd6bf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 50cfd9e91..2247cb3a7 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -65,7 +65,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index 50457cd3f..2a79bc06c 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -33,7 +33,7 @@ jobs: path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 5fc3a5514..3c5138630 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index cd8d7d4d0..4ea295d2f 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -47,6 +47,6 @@ jobs: severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: sarif_file: 'trivy-results.sarif' From 8260728b1ae8d81962eeece6a3bbc74d7996d0f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 19:54:34 +0000 Subject: [PATCH 11/21] build(deps): bump step-security/harden-runner from 2.19.0 to 2.19.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.19.0 to 2.19.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/8d3c67de8e2fe68ef647c8db1e6a09f647780f40...a5ad31d6a139d249332a2605b85202e8c0b78450) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 2 +- .github/workflows/deny.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/format.yml | 4 ++-- .github/workflows/fuzz.yml | 2 +- .github/workflows/integration-emu.yml | 2 +- .github/workflows/integration-tdx.yml | 4 ++-- .github/workflows/library.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/oss-fuzz.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/trivy.yml | 2 +- .github/workflows/weekly-cargo-update.yml | 2 +- .github/workflows/weekly-collateral-update.yml | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 559ebd6bf..bf403bf4d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index aacfbe378..0f3774261 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bd2d544ba..28f8635ad 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2247cb3a7..aefd3521d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,7 +21,7 @@ jobs: actions: read steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit @@ -80,7 +80,7 @@ jobs: # Install first since it's needed to build NASM - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 307a867ad..cb45ec068 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/integration-emu.yml b/.github/workflows/integration-emu.yml index 3880b3463..4f131ac26 100644 --- a/.github/workflows/integration-emu.yml +++ b/.github/workflows/integration-emu.yml @@ -129,7 +129,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/integration-tdx.yml b/.github/workflows/integration-tdx.yml index 1fdc203a4..6466dc16a 100644 --- a/.github/workflows/integration-tdx.yml +++ b/.github/workflows/integration-tdx.yml @@ -31,7 +31,7 @@ jobs: # - name: Install tools for sgx lib # run: sudo dnf group install 'Development Tools' | sudo dnf --enablerepo=powertools install ocaml ocaml-ocamlbuild wget rpm-build pkgcon - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit @@ -89,7 +89,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/library.yml b/.github/workflows/library.yml index ebb5f996e..054f5dee9 100644 --- a/.github/workflows/library.yml +++ b/.github/workflows/library.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0ffcfc66b..214040999 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: build_type: [release, debug] steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index 2a79bc06c..ee749066b 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -8,7 +8,7 @@ jobs: security-events: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3c5138630..f4d80b422 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 4ea295d2f..73612f29f 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -27,7 +27,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/weekly-cargo-update.yml b/.github/workflows/weekly-cargo-update.yml index 379da52a8..1df168235 100644 --- a/.github/workflows/weekly-cargo-update.yml +++ b/.github/workflows/weekly-cargo-update.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/weekly-collateral-update.yml b/.github/workflows/weekly-collateral-update.yml index 2c89ac17a..3a9cad49b 100644 --- a/.github/workflows/weekly-collateral-update.yml +++ b/.github/workflows/weekly-collateral-update.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit From 0160ef96ce698adead46daecc92cd0843969212c Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Wed, 6 May 2026 11:36:42 +0200 Subject: [PATCH 12/21] fix(migtd): remove init_event_log from rebinding attestation Per GHCI 1.5, MIGTD_DATA only contains tdinfo (type 0) and there is no init_event_log entry. The EventLogInit VDM element was sending the local event log as a substitute, which would fail verification against init tdinfo RTMRs since they belong to a different TD instance. Remove EventLogInit from the VDM ExchangeRebindAttestInfo request/response, drop the init_event_log parameter from authenticate_rebinding_old(), and remove the corresponding X.509 extension from the RATLS certificate. The rebind attest request element count is reduced from 7 to 6. Signed-off-by: Michal Tarnacki Co-authored-by: GitHub Copilot --- src/migtd/src/mig_policy.rs | 5 ----- src/migtd/src/migration/rebinding.rs | 8 +------- src/migtd/src/ratls/server_client.rs | 30 ---------------------------- src/migtd/src/spdm/spdm_req.rs | 15 -------------- src/migtd/src/spdm/spdm_rsp.rs | 15 -------------- src/migtd/src/spdm/spdm_vdm.rs | 2 +- 6 files changed, 2 insertions(+), 73 deletions(-) diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index 194366f71..65494116f 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -259,7 +259,6 @@ mod v2 { event_log_src: &[u8], mig_policy_src: &[u8], init_tdinfo: &[u8], - init_event_log: &[u8], servtd_ext_src: &[u8], ) -> Result, PolicyError> { let (mig_policy_src, peer_issuer_chain) = @@ -290,10 +289,6 @@ mod v2 { )) .ok_or(PolicyError::SvnMismatch)?; - // Verify init event log integrity against RTMRs from init tdinfo - verify_event_log(init_event_log, &get_rtmrs_from_tdinfo(&init_td_info)?) - .map_err(|_| PolicyError::InvalidEventLog)?; - // Use local policy's tcb_mapping with init tdinfo measurements let relative_reference = setup_evaluation_data_with_tdinfo(&init_td_info, policy)?; policy.policy_data.evaluate_policy_common( diff --git a/src/migtd/src/migration/rebinding.rs b/src/migtd/src/migration/rebinding.rs index 647a61e46..d8ad6936e 100644 --- a/src/migtd/src/migration/rebinding.rs +++ b/src/migtd/src/migration/rebinding.rs @@ -14,9 +14,9 @@ use ring::rand::{SecureRandom, SystemRandom}; use tdx_tdcall::tdx::{tdcall_servtd_rebind_approve, tdcall_vm_write}; use crate::migration::servtd_ext::read_servtd_ext; +use crate::migration::transport::*; #[cfg(feature = "spdm_attestation")] use crate::spdm; -use crate::{event_log, migration::transport::*}; use crate::migration::pre_session_data::{pre_session_data_exchange, LogErr}; @@ -408,18 +408,12 @@ async fn rebinding_old_prepare( // The TDINFO_STRUCT contains all the measurement fields needed for verification. let init_tdinfo = &init_migtd_data.init_tdinfo; - // Per GHCI 1.5: init_event_log is no longer part of MIGTD_DATA. - // Use local event log; RATLS cert still carries init_event_log extension - // for responder-side verification of init RTMRs. - let init_event_log = event_log::get_event_log().unwrap_or(&[]); - // TLS client let mut ratls_client = ratls::client_rebinding( transport, peer_data, &init_policy_hash, init_tdinfo, - init_event_log, &servtd_ext, ) .map_err(|_| { diff --git a/src/migtd/src/ratls/server_client.rs b/src/migtd/src/ratls/server_client.rs index 88d21a1a9..dca8e528a 100644 --- a/src/migtd/src/ratls/server_client.rs +++ b/src/migtd/src/ratls/server_client.rs @@ -185,7 +185,6 @@ pub fn client_rebinding( peer_data: Vec, init_policy_hash: &[u8], init_tdinfo: &[u8], - init_event_log: &[u8], servtd_ext: &ServtdExt, ) -> Result> { let signing_key = EcdsaPk::new().map_err(|e| { @@ -199,7 +198,6 @@ pub fn client_rebinding( &signing_key, init_policy_hash, init_tdinfo, - init_event_log, servtd_ext, ) .map_err(|e| { @@ -417,7 +415,6 @@ fn create_certificate_for_rebinding_old( signing_key: &EcdsaPk, init_policy_hash: &[u8], init_tdinfo: &[u8], - init_event_log: &[u8], servtd_ext: &ServtdExt, ) -> Result> { let pub_key = signing_key.public_key().map_err(|e| { @@ -529,27 +526,6 @@ fn create_certificate_for_rebinding_old( ); e })? - .add_extension( - Extension::new( - EXTNID_MIGTD_EVENT_LOG_INIT, - Some(false), - Some(&init_event_log), - ) - .map_err(|e| { - log::error!( - "gen_cert policy_v2 add_extension failed with error {:?}.\n", - e - ); - e - })?, - ) - .map_err(|e| { - log::error!( - "gen_cert policy_v2 add_extension for event log init failed with error {:?}.\n", - e - ); - e - })? .add_extension( Extension::new( EXTNID_MIGTD_INIT_POLICY_HASH, @@ -1001,11 +977,6 @@ mod verify { log::error!("Failed to find init tdinfo extension.\n"); CryptoError::ParseCertificate })?; - let init_event_log = - find_extension(extensions, &EXTNID_MIGTD_EVENT_LOG_INIT).ok_or_else(|| { - log::error!("Failed to find init event log extension.\n"); - CryptoError::ParseCertificate - })?; // Per GHCI 1.5: init_policy_hash is now mrowner from the initial TDINFO_STRUCT let init_policy_hash = find_extension(extensions, &EXTNID_MIGTD_INIT_POLICY_HASH) .ok_or_else(|| { @@ -1038,7 +1009,6 @@ mod verify { event_log, peer_data, init_tdinfo, - init_event_log, servtd_ext, ); diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index b913ede67..c3271bfe8 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -1024,21 +1024,6 @@ pub async fn send_and_receive_sdm_rebind_attest_info( .extend_from_slice(tdinfo_init) .ok_or(SPDM_STATUS_BUFFER_FULL)?; - //event log init - // Per GHCI 1.5: init_event_log is no longer in MIGTD_DATA; use local event log. - // NOTE: EventLogInit VDM element retained for wire compatibility with responder. - let event_log_init = crate::event_log::get_event_log().unwrap_or(&[]); - let event_log_init_element = VdmMessageElement { - element_type: VdmMessageElementType::EventLogInit, - length: event_log_init.len() as u32, - }; - cnt += event_log_init_element - .encode(&mut writer) - .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; - cnt += writer - .extend_from_slice(&event_log_init) - .ok_or(SPDM_STATUS_BUFFER_FULL)?; - //mig policy init hash // Per GHCI 1.5: policy_key is in tdinfo.mrowner; sent as init_policy_hash. // NOTE: MigPolicyInit VDM element name retained for wire compatibility. diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 8fbb760c4..56e68c77b 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -1074,20 +1074,6 @@ pub fn handle_exchange_rebind_attest_info_req( .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; let td_report_init_vec = td_report_init.to_vec(); - // event log init - let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; - if vdm_element.element_type != VdmMessageElementType::EventLogInit { - error!( - "Invalid VDM message element_type: {:x?}\n", - vdm_element.element_type - ); - return Err(SPDM_STATUS_INVALID_MSG_FIELD); - }; - let event_log_init = reader - .take(vdm_element.length as usize) - .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; - let event_log_init_vec = event_log_init.to_vec(); - // mig policy init hash let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; if vdm_element.element_type != VdmMessageElementType::MigPolicyInit { @@ -1129,7 +1115,6 @@ pub fn handle_exchange_rebind_attest_info_req( &event_log_src_vec, peer_data, &td_report_init_vec, - &event_log_init_vec, &servtd_ext_vec, ); if let Err(e) = &policy_check_result { diff --git a/src/migtd/src/spdm/spdm_vdm.rs b/src/migtd/src/spdm/spdm_vdm.rs index b4a1c72d2..87fa1d7ae 100644 --- a/src/migtd/src/spdm/spdm_vdm.rs +++ b/src/migtd/src/spdm/spdm_vdm.rs @@ -29,7 +29,7 @@ pub const VDM_MESSAGE_EXCHANGE_MIGRATION_ATTEST_INFO_REQ_ELEMENT_COUNT: u8 = 5; pub const VDM_MESSAGE_EXCHANGE_MIGRATION_ATTEST_INFO_RSP_ELEMENT_COUNT: u8 = 3; pub const VDM_MESSAGE_EXCHANGE_MIGRATION_INFO_REQ_ELEMENT_COUNT: u8 = 2; pub const VDM_MESSAGE_EXCHANGE_MIGRATION_INFO_RSP_ELEMENT_COUNT: u8 = 2; -pub const VDM_MESSAGE_EXCHANGE_REBIND_ATTEST_INFO_REQ_WITH_HISTORY_INFO_ELEMENT_COUNT: u8 = 7; +pub const VDM_MESSAGE_EXCHANGE_REBIND_ATTEST_INFO_REQ_WITH_HISTORY_INFO_ELEMENT_COUNT: u8 = 6; pub const VDM_MESSAGE_EXCHANGE_REBIND_ATTEST_INFO_RSP_ELEMENT_COUNT: u8 = 3; pub const VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_REQ_COUNT: u8 = 1; pub const VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_RSP_COUNT: u8 = 0; From ed3458225821720cd65f69c9a9e73798a17d66c8 Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Wed, 6 May 2026 11:15:34 +0200 Subject: [PATCH 13/21] feat(migtd): verify SERVTD_ATTR on destination before MSK write Add verify_servtd_attr() that reads CURR_SERVTD_ATTR via TDG.SERVTD.RD and compares it against the hardcoded EXPECTED_SERVTD_ATTR value. Call it on the SPDM responder (destination) path before set_mig_version/write_msk, and in the non-SPDM exchange_msk path for both source and destination. Per GHCI 1.5, both source and destination MigTDs must verify CURR_SERVTD_ATTR matches the expected value before any TDG.SERVTD.WR operations (mig_dec_key, mig_version), since the field is written by untrusted VMM during PREBIND. Signed-off-by: Michal Tarnacki Co-authored-by: GitHub Copilot --- src/migtd/src/migration/servtd_ext.rs | 19 +++++++++++++++++++ src/migtd/src/migration/session.rs | 10 ++++++++++ src/migtd/src/spdm/spdm_rsp.rs | 9 ++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/migtd/src/migration/servtd_ext.rs b/src/migtd/src/migration/servtd_ext.rs index 4eb2dfaa6..33abf95d8 100644 --- a/src/migtd/src/migration/servtd_ext.rs +++ b/src/migtd/src/migration/servtd_ext.rs @@ -146,6 +146,25 @@ pub fn read_servtd_ext( }) } +/// Verify that CURR_SERVTD_ATTR of the target TD matches the hardcoded expected value. +/// +/// Per GHCI 1.5: Both source and destination MigTDs must verify this before +/// any TDG.SERVTD.WR operations (mig_dec_key, mig_version). +pub fn verify_servtd_attr( + binding_handle: u64, + target_td_uuid: &[u64], +) -> Result<(), MigrationResult> { + let result = tdcall_servtd_rd(binding_handle, TDCS_FIELD_SERVTD_ATTR, target_td_uuid)?; + let actual_attr = result.content; + if actual_attr != EXPECTED_SERVTD_ATTR { + log::error!( + "SERVTD_ATTR mismatch: expected {EXPECTED_SERVTD_ATTR:#x}, got {actual_attr:#x}" + ); + return Err(MigrationResult::InvalidParameter); + } + Ok(()) +} + pub fn write_approved_servtd_ext_hash(servtd_ext_hash: &[u8]) -> Result<(), MigrationResult> { if servtd_ext_hash.len() != SHA384_DIGEST_SIZE { return Err(MigrationResult::InvalidParameter); diff --git a/src/migtd/src/migration/session.rs b/src/migtd/src/migration/session.rs index b3fc55c7e..ad6c0f6c1 100644 --- a/src/migtd/src/migration/session.rs +++ b/src/migtd/src/migration/session.rs @@ -6,6 +6,8 @@ use crate::migration::pre_session_data::pre_session_data_exchange; #[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] use crate::migration::rebinding::RebindingInfo; +#[cfg(not(feature = "spdm_attestation"))] +use crate::migration::servtd_ext::verify_servtd_attr; use crate::migration::transport::setup_transport; use crate::migration::transport::shutdown_transport; use crate::migration::transport::TransportType; @@ -1083,6 +1085,14 @@ pub async fn exchange_msk(info: &MigrationInformation) -> Result<()> { log::error!(migration_request_id = info.mig_info.mig_request_id; "exchange_msk: cal_mig_version error: {:?}\n", e); e })?; + verify_servtd_attr( + info.mig_info.binding_handle, + &info.mig_info.target_td_uuid, + ) + .map_err(|e| { + log::error!(migration_request_id = info.mig_info.mig_request_id; "exchange_msk: verify_servtd_attr error: {:?}\n", e); + e + })?; set_mig_version(&info.mig_info, mig_ver).map_err(|e| { log::error!(migration_request_id = info.mig_info.mig_request_id; "exchange_msk: set_mig_version error: {:?}\n", e); e diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 56e68c77b..dab15076d 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -9,8 +9,10 @@ use crate::migration::pre_session_data::local_peer_data; use crate::migration::rebinding::{ write_rebinding_session_token, write_servtd_rebind_attr, RebindingInfo, }; +#[cfg(not(feature = "policy_v2"))] +use crate::migration::servtd_ext::verify_servtd_attr; #[cfg(feature = "policy_v2")] -use crate::migration::servtd_ext::{write_approved_servtd_ext_hash, ServtdExt}; +use crate::migration::servtd_ext::{verify_servtd_attr, write_approved_servtd_ext_hash, ServtdExt}; use crate::{ event_log::get_event_log, migration::{ @@ -833,6 +835,11 @@ pub fn handle_exchange_mig_info_req( SpdmAppContextData::read(&mut reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; let exchange_information = exchange_info(&responder_app_context.migration_info, false)?; + verify_servtd_attr( + responder_app_context.migration_info.binding_handle, + &responder_app_context.migration_info.target_td_uuid, + )?; + let mig_ver = cal_mig_version(false, &exchange_information, &remote_information)?; set_mig_version(&responder_app_context.migration_info, mig_ver)?; write_msk( From 40de7e37f130d8eef99c2e9de5a993b52614bc86 Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Wed, 6 May 2026 10:54:18 +0200 Subject: [PATCH 14/21] feat(migtd): support tdinfo_init in MigtdMigrationInformation Per GHCI 1.5, the VMM must put migpolicy.policy_key and migpolicy.policy_svn in TDINFO.MROWNER and TDINFO.MROWNERCONFIG respectively, and may provide initMigtdData in the StartMigration request for subsequent migrations. This change implements: a) Allow VMM to pass initMigtdData via the StartMigration request. MigtdMigrationInformation now has has_init_data field at offset 9 and the parser accepts variable-length payload with init data. b) MigTD startup self-check: verify that own TDINFO.MROWNER equals SHA384(policy signing public key) and TDINFO.MROWNERCONFIG equals own policy_svn. Reject with InvalidPolicyError on mismatch. c) Migration flow verification: when VMM provides initMigtdData, verify that initMigtdData.MROWNER matches own policy signer key hash and initMigtdData.MROWNERCONFIG <= own policy_svn before proceeding with key exchange. d) SPDM requester for migration uses VMM-provided initMigtdData (when available) instead of always generating local TDINFO, enabling correct attestation for non-initial migrations. Closes: #822 Signed-off-by: Michal Tarnacki Co-authored-by: GitHub Copilot --- src/crypto/src/lib.rs | 19 +++ src/migtd/src/bin/migtd/main.rs | 15 ++ src/migtd/src/mig_policy.rs | 67 ++++++++ src/migtd/src/migration/data.rs | 23 ++- src/migtd/src/migration/init_data.rs | 120 ++++++++++++++ src/migtd/src/migration/mod.rs | 32 +++- src/migtd/src/migration/rebinding.rs | 231 +++++++++++++++------------ src/migtd/src/migration/session.rs | 65 +++++++- src/migtd/src/spdm/spdm_req.rs | 36 ++++- src/policy/src/v2/policy.rs | 4 + 10 files changed, 492 insertions(+), 120 deletions(-) create mode 100644 src/migtd/src/migration/init_data.rs diff --git a/src/crypto/src/lib.rs b/src/crypto/src/lib.rs index 477ec9b7a..6ae60323e 100644 --- a/src/crypto/src/lib.rs +++ b/src/crypto/src/lib.rs @@ -100,6 +100,25 @@ pub fn pem_cert_to_der(cert: &[u8]) -> Result> { CertificateDer::from_pem_slice(cert).map_err(|_| Error::DecodePemCert) } +/// Returns the SHA-384 hash of the leaf certificate's public key from a PEM cert chain. +/// Per GHCI 1.5: this hash is placed in tdinfo.MROWNER as the policy signing key identifier. +pub fn get_policy_signer_key_hash(cert_chain_pem: &[u8]) -> Result<[u8; SHA384_DIGEST_SIZE]> { + let cert_chain = extract_cert_chain_from_pem(cert_chain_pem)?; + if cert_chain.is_empty() { + return Err(Error::CertChainVerification( + "No certificates found in chain".into(), + )); + } + let leaf_cert = &cert_chain[0]; + let cert = + x509::Certificate::from_der(leaf_cert.as_ref()).map_err(|_| Error::ParseCertificate)?; + let public_key = extract_public_key_from_cert(&cert)?; + let hash_vec = hash::digest_sha384(&public_key).map_err(|_| Error::CalculateDigest)?; + let mut hash = [0u8; SHA384_DIGEST_SIZE]; + hash.copy_from_slice(&hash_vec); + Ok(hash) +} + /// Verifies a certificate chain and then verifies a message signature pub fn verify_cert_chain_and_signature( cert_chain_pem: &[u8], diff --git a/src/migtd/src/bin/migtd/main.rs b/src/migtd/src/bin/migtd/main.rs index 040e2b138..c491e71e7 100644 --- a/src/migtd/src/bin/migtd/main.rs +++ b/src/migtd/src/bin/migtd/main.rs @@ -275,6 +275,21 @@ fn get_policy_and_measure(event_log: &mut [u8]) { let version = initialize_policy(); + // Per GHCI 1.5: Verify own TDINFO.MROWNER/MROWNERCONFIG matches policy key/SVN + // Skip in AzCVMEmu mode — emulator uses mock TD reports where VMM does not + // populate MROWNER/MROWNERCONFIG. + #[cfg(not(feature = "AzCVMEmu"))] + { + use migtd::mig_policy; + if let Err(e) = mig_policy::verify_own_tdinfo() { + log::error!("TDINFO policy binding verification failed: {:?}\n", e); + panic_with_guest_crash_reg_report( + MigrationResult::InvalidPolicyError as u64, + b"TDINFO MROWNER/MROWNERCONFIG mismatch with policy", + ); + } + } + let event_data = version.as_bytes(); // Measure and extend the migration policy to RTMR diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index 65494116f..4f2e75119 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -708,6 +708,73 @@ mod v2 { }) } + /// Per GHCI 1.5: Verify that own TDINFO.MROWNER matches policy signing key hash + /// and TDINFO.MROWNERCONFIG matches policy SVN. + /// Must be called at MigTD startup to ensure VMM correctly provisioned the TD. + pub fn verify_own_tdinfo() -> Result<(), PolicyError> { + use crate::config::get_policy_issuer_chain; + + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + let policy_svn = policy.policy_data.get_policy_svn(); + + // Get own TDINFO from TDReport + let tdx_report = tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) + .map_err(|_| PolicyError::GetTdxReport)?; + let td_info = &tdx_report.td_info; + + // Verify MROWNERCONFIG == policy_svn (stored as little-endian u32 in first 4 bytes, + // remaining 44 bytes must be zero) + let mut expected_mrownerconfig = [0u8; SHA384_DIGEST_SIZE]; + expected_mrownerconfig[..4].copy_from_slice(&policy_svn.to_le_bytes()); + if td_info.mrownerconfig != expected_mrownerconfig { + return Err(PolicyError::SvnMismatch); + } + + // Verify MROWNER == SHA384(policy signing public key) + let policy_issuer_chain = get_policy_issuer_chain().ok_or(PolicyError::InvalidParameter)?; + let policy_key_hash = crypto::get_policy_signer_key_hash(policy_issuer_chain) + .map_err(|_| PolicyError::InvalidCollateral)?; + if td_info.mrowner != policy_key_hash { + return Err(PolicyError::PolicyHashMismatch); + } + + Ok(()) + } + + /// Per GHCI 1.5: Verify initMigtdData.MROWNER matches own policy signer key hash + /// and initMigtdData.MROWNERCONFIG <= own policy SVN. + pub fn verify_init_migtd_data_policy_binding( + init_data: &crate::migration::init_data::InitData, + ) -> Result<(), PolicyError> { + use crate::config::get_policy_issuer_chain; + + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + let my_policy_svn = policy.policy_data.get_policy_svn(); + + // Check MROWNER == own policy signer key hash + let policy_issuer_chain = get_policy_issuer_chain().ok_or(PolicyError::InvalidParameter)?; + let policy_key_hash = crypto::get_policy_signer_key_hash(policy_issuer_chain) + .map_err(|_| PolicyError::InvalidCollateral)?; + if init_data.mrowner() != policy_key_hash { + return Err(PolicyError::PolicyHashMismatch); + } + + // Check MROWNERCONFIG (init policy_svn) <= my policy_svn + let init_mrownerconfig = init_data.mrownerconfig(); + let mut init_svn_bytes = [0u8; 4]; + init_svn_bytes.copy_from_slice(&init_mrownerconfig[..4]); + let init_policy_svn = u32::from_le_bytes(init_svn_bytes); + // Remaining 44 bytes should be zero + if init_mrownerconfig[4..] != [0u8; SHA384_DIGEST_SIZE - 4] { + return Err(PolicyError::InvalidParameter); + } + if init_policy_svn > my_policy_svn { + return Err(PolicyError::SvnMismatch); + } + + Ok(()) + } + #[test] fn test_unix_to_iso8601() { let timestamp = 1704067200; // Corresponds to 2024-01-01T00:00:00Z diff --git a/src/migtd/src/migration/data.rs b/src/migtd/src/migration/data.rs index 0db53e4bb..82c99ca9f 100644 --- a/src/migtd/src/migration/data.rs +++ b/src/migtd/src/migration/data.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: BSD-2-Clause-Patent +#[cfg(feature = "policy_v2")] +use crate::migration::init_data::InitData; #[cfg(all(feature = "main", feature = "vmcall-raw", feature = "policy_v2"))] use crate::migration::rebinding::RebindingInfo; @@ -267,6 +269,8 @@ pub enum WaitForRequestResponse { pub struct MigrationInformation { pub mig_info: MigtdMigrationInformation, + #[cfg(feature = "policy_v2")] + pub init_migtd_data: Option, #[cfg(all( not(feature = "vmcall-raw"), any(feature = "vmcall-vsock", feature = "virtio-vsock") @@ -356,9 +360,17 @@ fn create_migration_information( mig_socket_hob: Option<&[u8]>, policy_info_hob: Option<&[u8]>, ) -> Option { - let mig_info = hob_lib::get_guid_data(mig_info_hob?)? - .pread::(0) - .ok()?; + let mig_info_data = hob_lib::get_guid_data(mig_info_hob?)?; + let mig_info = mig_info_data.pread::(0).ok()?; + + // Per GHCI 1.5: if has_init_data == 1, InitData blob follows MigtdMigrationInformation + #[cfg(feature = "policy_v2")] + let init_migtd_data = if mig_info.has_init_data == 1 { + let offset = size_of::(); + InitData::read_from_bytes(mig_info_data.get(offset..)?) + } else { + None + }; #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] let mig_socket_info = hob_lib::get_guid_data(mig_socket_hob?)? @@ -380,6 +392,8 @@ fn create_migration_information( Some(MigrationInformation { mig_info, + #[cfg(feature = "policy_v2")] + init_migtd_data, #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] mig_socket_info, mig_policy, @@ -730,7 +744,8 @@ mod test { let mig_info = MigtdMigrationInformation { mig_request_id: 0, migration_source: 1, - _pad: [0, 0, 0, 0, 0, 0, 0], + has_init_data: 0, + _reserved: [0; 6], target_td_uuid: [0, 0, 0, 0], binding_handle: 0, mig_policy_id: 0, diff --git a/src/migtd/src/migration/init_data.rs b/src/migtd/src/migration/init_data.rs new file mode 100644 index 000000000..abdf6fb0a --- /dev/null +++ b/src/migtd/src/migration/init_data.rs @@ -0,0 +1,120 @@ +// Copyright (c) 2025 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +use alloc::vec::Vec; +use crypto::SHA384_DIGEST_SIZE; + +pub(crate) const MIGTD_DATA_SIGNATURE: &[u8] = b"MIGTDATA"; +pub(crate) const MIGTD_DATA_TYPE_TDINFO: u32 = 0; + +pub struct InitData { + /// The TDINFO_STRUCT of the initial MigTD (per GHCI 1.5, MIGTD_DATA type 0). + pub init_tdinfo: Vec, +} + +impl InitData { + /// TDINFO_STRUCT field offsets and sizes (per TDX Module ABI). + const TDINFO_MROWNER_OFFSET: usize = 112; // attributes(8) + xfam(8) + mrtd(48) + mrconfig_id(48) + const TDINFO_MROWNERCONFIG_OFFSET: usize = 160; // MROWNER_OFFSET + 48 + const TDINFO_FIELD_SIZE: usize = SHA384_DIGEST_SIZE; + const TDINFO_MIN_SIZE: usize = 512; + + /// Extract mrowner from the TDINFO_STRUCT. + /// Per GHCI 1.5: VMM puts migpolicy.policy_key in tdinfo.mrowner. + pub fn mrowner(&self) -> &[u8] { + &self.init_tdinfo + [Self::TDINFO_MROWNER_OFFSET..Self::TDINFO_MROWNER_OFFSET + Self::TDINFO_FIELD_SIZE] + } + + /// Extract mrownerconfig from the TDINFO_STRUCT. + /// Per GHCI 1.5: VMM puts migpolicy.policy_svn in tdinfo.mrownerconfig. + pub fn mrownerconfig(&self) -> &[u8] { + &self.init_tdinfo[Self::TDINFO_MROWNERCONFIG_OFFSET + ..Self::TDINFO_MROWNERCONFIG_OFFSET + Self::TDINFO_FIELD_SIZE] + } + + pub fn read_from_bytes(b: &[u8]) -> Option { + if b.len() < 20 || &b[..8] != MIGTD_DATA_SIGNATURE { + return None; + } + + let version = u32::from_le_bytes(b[8..12].try_into().unwrap()); + let length = u32::from_le_bytes(b[12..16].try_into().unwrap()); + let num_entries = u32::from_le_bytes(b[16..20].try_into().unwrap()); + + // Per GHCI 1.5: version must be 0x00010000, numberOfEntry must be 1 (tdinfo) + if version != 0x00010000 || b.len() < length as usize || num_entries != 1 { + return None; + } + + let entry = MigtdDataEntry::read_from_bytes(&b[20..])?; + if entry.r#type != MIGTD_DATA_TYPE_TDINFO { + return None; + } + + if entry.value.len() < Self::TDINFO_MIN_SIZE { + return None; + } + + Some(Self { + init_tdinfo: entry.value.to_vec(), + }) + } + + pub fn write_into_bytes(&self, buf: &mut Vec) { + let start_len = buf.len(); + buf.extend_from_slice(MIGTD_DATA_SIGNATURE); + buf.extend_from_slice(&0x00010000u32.to_le_bytes()); // Version + + // Placeholder for length. + buf.extend_from_slice(&0u32.to_le_bytes()); + + // Per GHCI 1.5: numberOfEntry = 1, entry type 0 = tdinfo + buf.extend_from_slice(&1u32.to_le_bytes()); // num_entries + + buf.extend_from_slice(&MIGTD_DATA_TYPE_TDINFO.to_le_bytes()); + buf.extend_from_slice(&(self.init_tdinfo.len() as u32).to_le_bytes()); + buf.extend_from_slice(&self.init_tdinfo); + + let total_size = (buf.len() - start_len) as u32; + + // Update length field + let length_offset = start_len + 12; + buf[length_offset..length_offset + 4].copy_from_slice(&total_size.to_le_bytes()); + } + + pub fn get_from_local(report_data: &[u8; 64]) -> Option { + let report = tdx_tdcall::tdreport::tdcall_report(report_data).ok()?; + Some(Self { + init_tdinfo: report.td_info.as_bytes().to_vec(), + }) + } +} + +pub struct MigtdDataEntry<'a> { + pub r#type: u32, + pub length: u32, + pub value: &'a [u8], +} + +impl<'a> MigtdDataEntry<'a> { + pub fn read_from_bytes(b: &'a [u8]) -> Option { + if b.len() < 8 { + return None; + } + + let r#type = u32::from_le_bytes(b[0..4].try_into().unwrap()); + let length = u32::from_le_bytes(b[4..8].try_into().unwrap()); + + if b.len() < length as usize + 8 { + return None; + } + + Some(Self { + r#type, + length, + value: &b[8..8 + length as usize], + }) + } +} diff --git a/src/migtd/src/migration/mod.rs b/src/migtd/src/migration/mod.rs index d800b4ac0..e612b49cb 100644 --- a/src/migtd/src/migration/mod.rs +++ b/src/migtd/src/migration/mod.rs @@ -4,6 +4,8 @@ pub mod data; pub mod event; +#[cfg(feature = "policy_v2")] +pub mod init_data; pub mod logging; #[cfg(feature = "policy_v2")] pub mod pre_session_data; @@ -152,7 +154,11 @@ pub struct MigtdMigrationInformation { // If set, current MigTD is MigTD-s else current MigTD is MigTD-d pub migration_source: u8, - _pad: [u8; 7], + + // Per GHCI 1.5: hasInitMigtdData — true if initMigtdData follows at offset 56 + pub has_init_data: u8, + + _reserved: [u8; 6], // UUID of target TD pub target_td_uuid: [u64; 4], @@ -171,7 +177,29 @@ pub struct MigtdMigrationInformation { } #[cfg(feature = "vmcall-raw")] -impl_read_from_bytes!(MigtdMigrationInformation); +impl MigtdMigrationInformation { + pub fn read_from_bytes( + data_length: u32, + payload: &[u8], + ) -> core::result::Result { + let fixed_size = core::mem::size_of::() as u32; + if (data_length as usize) < fixed_size as usize { + return Err(MigrationResult::InvalidParameter); + } + let info: Self = payload + .pread(0) + .map_err(|_| MigrationResult::InvalidParameter)?; + // Reject trailing bytes unless has_init_data is set + if data_length > fixed_size && info.has_init_data != 1 { + return Err(MigrationResult::InvalidParameter); + } + // Reject reserved fields that must be zero + if info._reserved != [0; 6] { + return Err(MigrationResult::InvalidParameter); + } + Ok(info) + } +} #[repr(C)] #[derive(Debug, Pread, Pwrite)] diff --git a/src/migtd/src/migration/rebinding.rs b/src/migtd/src/migration/rebinding.rs index d8ad6936e..9228577f9 100644 --- a/src/migtd/src/migration/rebinding.rs +++ b/src/migtd/src/migration/rebinding.rs @@ -8,7 +8,6 @@ use core::time::Duration; use crypto::{ tls::SecureChannel, x509::{Certificate, Decode}, - SHA384_DIGEST_SIZE, }; use ring::rand::{SecureRandom, SystemRandom}; use tdx_tdcall::tdx::{tdcall_servtd_rebind_approve, tdcall_vm_write}; @@ -39,8 +38,8 @@ const TDCS_FIELD_WRITE_MASK: u64 = u64::MAX; const TLS_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds // FIXME: Need VMM provide socket information -const MIGTD_DATA_SIGNATURE: &[u8] = b"MIGTDATA"; -const MIGTD_DATA_TYPE_TDINFO: u32 = 0; +pub use super::init_data::{InitData, MigtdDataEntry}; +pub(crate) use super::init_data::{MIGTD_DATA_SIGNATURE, MIGTD_DATA_TYPE_TDINFO}; #[repr(C)] pub struct RebindingToken { @@ -121,115 +120,145 @@ impl RebindingInfo { } } -pub struct InitData { - /// The TDINFO_STRUCT of the initial MigTD (per GHCI 1.5, MIGTD_DATA type 0). - pub init_tdinfo: Vec, -} - -impl InitData { - /// TDINFO_STRUCT field offsets and sizes (per TDX Module ABI). - const TDINFO_MROWNER_OFFSET: usize = 112; // attributes(8) + xfam(8) + mrtd(48) + mrconfig_id(48) - const TDINFO_MROWNERCONFIG_OFFSET: usize = 160; // MROWNER_OFFSET + 48 - const TDINFO_FIELD_SIZE: usize = SHA384_DIGEST_SIZE; - const TDINFO_MIN_SIZE: usize = 512; - - /// Extract mrowner from the TDINFO_STRUCT. - /// Per GHCI 1.5: VMM puts migpolicy.policy_key in tdinfo.mrowner. - pub fn mrowner(&self) -> &[u8] { - &self.init_tdinfo - [Self::TDINFO_MROWNER_OFFSET..Self::TDINFO_MROWNER_OFFSET + Self::TDINFO_FIELD_SIZE] - } - - /// Extract mrownerconfig from the TDINFO_STRUCT. - /// Per GHCI 1.5: VMM puts migpolicy.policy_svn in tdinfo.mrownerconfig. - pub fn mrownerconfig(&self) -> &[u8] { - &self.init_tdinfo[Self::TDINFO_MROWNERCONFIG_OFFSET - ..Self::TDINFO_MROWNERCONFIG_OFFSET + Self::TDINFO_FIELD_SIZE] - } - - pub fn read_from_bytes(b: &[u8]) -> Option { - if b.len() < 20 || &b[..8] != MIGTD_DATA_SIGNATURE { - return None; - } - - let version = u32::from_le_bytes(b[8..12].try_into().unwrap()); - let length = u32::from_le_bytes(b[12..16].try_into().unwrap()); - let num_entries = u32::from_le_bytes(b[16..20].try_into().unwrap()); - - // Per GHCI 1.5: version must be 0x00010000, numberOfEntry must be 1 (tdinfo) - if version != 0x00010000 || b.len() < length as usize || num_entries != 1 { - return None; - } - - let entry = MigtdDataEntry::read_from_bytes(&b[20..])?; - if entry.r#type != MIGTD_DATA_TYPE_TDINFO { - return None; - } +pub use super::init_data::{InitData, MigtdDataEntry}; +pub(crate) use super::init_data::{MIGTD_DATA_SIGNATURE, MIGTD_DATA_TYPE_TDINFO}; - if entry.value.len() < Self::TDINFO_MIN_SIZE { - return None; - } - - Some(Self { - init_tdinfo: entry.value.to_vec(), - }) - } - - pub fn write_into_bytes(&self, buf: &mut Vec) { - let start_len = buf.len(); - buf.extend_from_slice(MIGTD_DATA_SIGNATURE); - buf.extend_from_slice(&0x00010000u32.to_le_bytes()); // Version - - // Placeholder for length. - buf.extend_from_slice(&0u32.to_le_bytes()); - - // Per GHCI 1.5: numberOfEntry = 1, entry type 0 = tdinfo - buf.extend_from_slice(&1u32.to_le_bytes()); // num_entries +pub(super) async fn rebinding_old_pre_session_data_exchange( + transport: &mut TransportType, + init_tdinfo: &[u8], +) -> Result, MigrationResult> { + let version = exchange_hello_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: exchange_hello_packet error: {:?}\n", + e + ); + e + })?; + log::info!("Pre-Session-Message Version: 0x{:04x}\n", version); - buf.extend_from_slice(&MIGTD_DATA_TYPE_TDINFO.to_le_bytes()); - buf.extend_from_slice(&(self.init_tdinfo.len() as u32).to_le_bytes()); - buf.extend_from_slice(&self.init_tdinfo); + let policy = config::get_policy() + .ok_or(MigrationResult::InvalidParameter) + .map_err(|e| { + log::error!("pre_session_data_exchange: get_policy error: {:?}\n", e); + e + })?; + send_pre_session_data_packet(policy, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + let remote_policy = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; - let total_size = (buf.len() - start_len) as u32; + send_pre_session_data_packet(init_tdinfo, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; - // Update length field - let length_offset = start_len + 12; - buf[length_offset..length_offset + 4].copy_from_slice(&total_size.to_le_bytes()); - } + send_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: send_start_session_packet error: {:?}\n", + e + ); + e + })?; + receive_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_start_session_packet error: {:?}\n", + e + ); + e + })?; - pub fn get_from_local(report_data: &[u8; 64]) -> Option { - let report = tdx_tdcall::tdreport::tdcall_report(report_data).ok()?; - Some(Self { - init_tdinfo: report.td_info.as_bytes().to_vec(), - }) - } + Ok(remote_policy) } -pub struct MigtdDataEntry<'a> { - pub r#type: u32, - pub length: u32, - pub value: &'a [u8], -} +pub(super) async fn rebinding_new_pre_session_data_exchange( + transport: &mut TransportType, +) -> Result, MigrationResult> { + let version = exchange_hello_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: exchange_hello_packet error: {:?}\n", + e + ); + e + })?; + log::info!("Pre-Session-Message Version: 0x{:04x}\n", version); -impl<'a> MigtdDataEntry<'a> { - pub fn read_from_bytes(b: &'a [u8]) -> Option { - if b.len() < 8 { - return None; - } + let policy = config::get_policy() + .ok_or(MigrationResult::InvalidParameter) + .map_err(|e| { + log::error!("pre_session_data_exchange: get_policy error: {:?}\n", e); + e + })?; + send_pre_session_data_packet(policy, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + let remote_policy = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; - let r#type = u32::from_le_bytes(b[0..4].try_into().unwrap()); - let length = u32::from_le_bytes(b[4..8].try_into().unwrap()); + let init_tdinfo = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: receive init_tdinfo error: {:?}\n", + e + ); + e + })?; - if b.len() < length as usize + 8 { - return None; - } + send_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: send_start_session_packet error: {:?}\n", + e + ); + e + })?; + receive_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_start_session_packet error: {:?}\n", + e + ); + e + })?; - Some(Self { - r#type, - length, - value: &b[8..8 + length as usize], - }) - } + // FIXME: Refactor the TLS verification callback to enable easier access to pre-session data. + let mut policy_buffer = Vec::new(); + policy_buffer.extend_from_slice(&(remote_policy.len() as u32).to_le_bytes()); + policy_buffer.extend_from_slice(&remote_policy); + policy_buffer.extend_from_slice(&(init_tdinfo.len() as u32).to_le_bytes()); + policy_buffer.extend_from_slice(&init_tdinfo); + + Ok(policy_buffer) } pub async fn start_rebinding( diff --git a/src/migtd/src/migration/session.rs b/src/migtd/src/migration/session.rs index ad6c0f6c1..a3314ae9c 100644 --- a/src/migtd/src/migration/session.rs +++ b/src/migtd/src/migration/session.rs @@ -399,9 +399,50 @@ fn parse_request( match op { DataStatusOperation::StartMigration => { - decode_and_dispatch!(MigtdMigrationInformation, |mig_info| { - WaitForRequestResponse::StartMigration(MigrationInformation { mig_info }) - }) + #[cfg(feature = "policy_v2")] + { + match MigtdMigrationInformation::read_from_bytes(data_length, slice) { + Ok(mig_info) => { + use crate::migration::init_data::InitData; + let init_migtd_data = if mig_info.has_init_data == 1 { + let fixed_size = core::mem::size_of::(); + Some( + InitData::read_from_bytes(&slice[fixed_size..]) + .ok_or(MigrationResult::InvalidParameter) + .map_err(|status| { + log_request_error!( + request_id, + "wait_for_request: StartMigration failed to decode init_migtd_data\n" + ); + status + })?, + ) + } else { + None + }; + try_accept_request( + mig_info.mig_request_id, + WaitForRequestResponse::StartMigration(MigrationInformation { + mig_info, + init_migtd_data, + }), + ) + } + Err(status) => { + log_request_error!( + request_id, + "wait_for_request: StartMigration failed to decode payload\n" + ); + reject_request(pending_error_report, request_id, status) + } + } + } + #[cfg(not(feature = "policy_v2"))] + { + decode_and_dispatch!(MigtdMigrationInformation, |mig_info| { + WaitForRequestResponse::StartMigration(MigrationInformation { mig_info }) + }) + } } DataStatusOperation::StartRebinding => { #[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] @@ -674,7 +715,7 @@ pub async fn get_migtd_data( data: &mut Vec, request_id: u64, ) -> Result<()> { - use crate::migration::rebinding::InitData; + use crate::migration::init_data::InitData; let init_data = InitData::get_from_local(additional_data).ok_or_else(|| { log::error!( migration_request_id = request_id; @@ -950,7 +991,9 @@ async fn migration_src_exchange_msk( &mut spdm_requester, &info.mig_info, #[cfg(feature = "policy_v2")] - peer_data, + info.init_migtd_data.as_ref(), + #[cfg(feature = "policy_v2")] + remote_policy, ), ) .await @@ -1021,6 +1064,18 @@ async fn migration_dst_exchange_msk( #[cfg(feature = "main")] pub async fn exchange_msk(info: &MigrationInformation) -> Result<()> { + // Per GHCI 1.5: if VMM provided initMigtdData, verify policy binding + #[cfg(feature = "policy_v2")] + if let Some(ref init_data) = info.init_migtd_data { + crate::mig_policy::verify_init_migtd_data_policy_binding(init_data).map_err(|e| { + log::error!( + migration_request_id = info.mig_info.mig_request_id; + "exchange_msk: initMigtdData policy binding verification failed: {:?}\n", e + ); + MigrationResult::PolicyUnsatisfiedError + })?; + } + let mut transport = setup_transport( info.mig_info.mig_request_id, #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index c3271bfe8..a8428e93e 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -90,7 +90,8 @@ pub fn spdm_requester pub async fn spdm_requester_transfer_msk( spdm_requester: &mut RequesterContext, mig_info: &MigtdMigrationInformation, - #[cfg(feature = "policy_v2")] peer_data: Vec, + #[cfg(feature = "policy_v2")] init_migtd_data: Option<&crate::migration::init_data::InitData>, + #[cfg(feature = "policy_v2")] remote_policy: Vec, ) -> Result<(), SpdmStatus> { Box::pin(spdm_requester.send_receive_spdm_version()).await?; Box::pin(spdm_requester.send_receive_spdm_capability()).await?; @@ -108,7 +109,9 @@ pub async fn spdm_requester_transfer_msk( mig_info, session_id, #[cfg(feature = "policy_v2")] - peer_data, + init_migtd_data, + #[cfg(feature = "policy_v2")] + remote_policy, )) .await?; @@ -296,7 +299,8 @@ pub async fn send_and_receive_sdm_migration_attest_info( spdm_requester: &mut RequesterContext, mig_info: &MigtdMigrationInformation, session_id: u32, - #[cfg(feature = "policy_v2")] peer_data: Vec, + #[cfg(feature = "policy_v2")] init_migtd_data: Option<&crate::migration::init_data::InitData>, + #[cfg(feature = "policy_v2")] remote_policy: Vec, ) -> SpdmResult { if spdm_requester.common.provision_info.my_pub_key.is_none() || spdm_requester.common.provision_info.peer_pub_key.is_none() @@ -419,11 +423,27 @@ pub async fn send_and_receive_sdm_migration_attest_info( .ok_or(SPDM_STATUS_BUFFER_FULL)?; } - // Init TDINFO: local MigTD TDINFO_STRUCT for SERVTD_HASH verification by peer + // Init TDINFO: use VMM-provided initMigtdData if available, otherwise local { - let report = tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) - .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; - let tdinfo_init = report.td_info.as_bytes(); + #[cfg(feature = "policy_v2")] + let tdinfo_init_vec; + #[cfg(feature = "policy_v2")] + let tdinfo_init: &[u8] = if let Some(init_data) = init_migtd_data { + &init_data.init_tdinfo + } else { + let report = tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + tdinfo_init_vec = report.td_info.as_bytes().to_vec(); + &tdinfo_init_vec + }; + #[cfg(not(feature = "policy_v2"))] + let tdinfo_init = { + let report = tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + report.td_info.as_bytes().to_vec() + }; + #[cfg(not(feature = "policy_v2"))] + let tdinfo_init: &[u8] = &tdinfo_init; let tdinfo_init_element = VdmMessageElement { element_type: VdmMessageElementType::TdReportInit, length: tdinfo_init.len() as u32, @@ -991,7 +1011,7 @@ pub async fn send_and_receive_sdm_rebind_attest_info( //SERVTD_EXT let binding_handle = rebind_info.binding_handle; let target_td_uuid = &rebind_info.target_td_uuid; - let local_data = crate::migration::rebinding::InitData::get_from_local(&[0u8; 64]) + let local_data = crate::migration::init_data::InitData::get_from_local(&[0u8; 64]) .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; let init_migtd_data = rebind_info.init_migtd_data.as_ref().unwrap_or(&local_data); diff --git a/src/policy/src/v2/policy.rs b/src/policy/src/v2/policy.rs index 0aee56f85..721fb2ba9 100644 --- a/src/policy/src/v2/policy.rs +++ b/src/policy/src/v2/policy.rs @@ -295,6 +295,10 @@ impl<'a> PolicyData<'a> { !self.id.is_empty() && self.version == "2.0" } + pub fn get_policy_svn(&self) -> u32 { + self.policy_svn + } + pub fn evaluate_policy_forward( &self, value: &PolicyEvaluationInfo, From 7711144ecdb52d7a62db0f6b53306b9190a5fa6a Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Wed, 13 May 2026 15:00:26 +0200 Subject: [PATCH 15/21] fix(servtd_ext): verify SERVTD_ATTR against INIT_ATTR from TDINFO Per GHCI 1.5, CURR_SERVTD_ATTR must match the SERVTD_ATTR value provided in MigTDData's TDINFO during migrate or rebind operations. Previously, both verify_servtd_attr() and read_servtd_ext() only compared against the hardcoded EXPECTED_SERVTD_ATTR constant (0). While all bits are currently fixed-0, this will break if IGNORE flags (bits 40:32) or future attribute bits become non-zero. Add a dynamic check that also compares CURR_SERVTD_ATTR against INIT_ATTR (read via TDCS_FIELD_SERVTD_INIT_ATTR) to ensure forward-compatibility with non-zero SERVTD_ATTR values. Closes: #831 Signed-off-by: Stanislaw Grams --- src/migtd/src/migration/servtd_ext.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/migtd/src/migration/servtd_ext.rs b/src/migtd/src/migration/servtd_ext.rs index 33abf95d8..740b95deb 100644 --- a/src/migtd/src/migration/servtd_ext.rs +++ b/src/migtd/src/migration/servtd_ext.rs @@ -123,11 +123,19 @@ pub fn read_servtd_ext( read_field(TDCS_FIELD_SERVTD_INFO_HASH, 8, &mut cur_servtd_info_hash)?; read_field(TDCS_FIELD_SERVTD_ATTR, 8, &mut cur_servtd_attr)?; - // Verify CURR_SERVTD_ATTR matches the hardcoded expected value per GHCI 1.5. + // Verify CURR_SERVTD_ATTR matches both the hardcoded expected value and the + // INIT_ATTR from MigTDData's TDINFO per GHCI 1.5. let actual_attr = u64::from_le_bytes(cur_servtd_attr); + let expected_init_attr = u64::from_le_bytes(init_attr); if actual_attr != EXPECTED_SERVTD_ATTR { log::error!( - "SERVTD_ATTR mismatch: expected {EXPECTED_SERVTD_ATTR:#x}, got {actual_attr:#x}" + "SERVTD_ATTR mismatch vs hardcoded: expected {EXPECTED_SERVTD_ATTR:#x}, got {actual_attr:#x}" + ); + return Err(MigrationResult::InvalidParameter); + } + if actual_attr != expected_init_attr { + log::error!( + "SERVTD_ATTR mismatch vs INIT_ATTR: expected {expected_init_attr:#x}, got {actual_attr:#x}" ); return Err(MigrationResult::InvalidParameter); } @@ -146,7 +154,8 @@ pub fn read_servtd_ext( }) } -/// Verify that CURR_SERVTD_ATTR of the target TD matches the hardcoded expected value. +/// Verify that CURR_SERVTD_ATTR of the target TD matches both the hardcoded +/// expected value and the INIT_ATTR from MigTDData's TDINFO. /// /// Per GHCI 1.5: Both source and destination MigTDs must verify this before /// any TDG.SERVTD.WR operations (mig_dec_key, mig_version). @@ -158,7 +167,17 @@ pub fn verify_servtd_attr( let actual_attr = result.content; if actual_attr != EXPECTED_SERVTD_ATTR { log::error!( - "SERVTD_ATTR mismatch: expected {EXPECTED_SERVTD_ATTR:#x}, got {actual_attr:#x}" + "SERVTD_ATTR mismatch vs hardcoded: expected {EXPECTED_SERVTD_ATTR:#x}, got {actual_attr:#x}" + ); + return Err(MigrationResult::InvalidParameter); + } + + let init_result = + tdcall_servtd_rd(binding_handle, TDCS_FIELD_SERVTD_INIT_ATTR, target_td_uuid)?; + let expected_init_attr = init_result.content; + if actual_attr != expected_init_attr { + log::error!( + "SERVTD_ATTR mismatch vs INIT_ATTR: expected {expected_init_attr:#x}, got {actual_attr:#x}" ); return Err(MigrationResult::InvalidParameter); } From 76bd67ff39817287da43eb3315a2733cdecea7bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 19:54:15 +0000 Subject: [PATCH 16/21] build(deps): bump step-security/harden-runner from 2.19.1 to 2.19.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.19.1 to 2.19.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/a5ad31d6a139d249332a2605b85202e8c0b78450...9ca718d3bf646d6534007c269a635b3e54cadf99) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 2 +- .github/workflows/deny.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/format.yml | 4 ++-- .github/workflows/fuzz.yml | 2 +- .github/workflows/integration-emu.yml | 2 +- .github/workflows/integration-tdx.yml | 4 ++-- .github/workflows/library.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/oss-fuzz.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/trivy.yml | 2 +- .github/workflows/weekly-cargo-update.yml | 2 +- .github/workflows/weekly-collateral-update.yml | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf403bf4d..93e10eb18 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 0f3774261..207a914cf 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 28f8635ad..126fdecf9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index aefd3521d..75c01967b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,7 +21,7 @@ jobs: actions: read steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit @@ -80,7 +80,7 @@ jobs: # Install first since it's needed to build NASM - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index cb45ec068..2b8f73ff9 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/integration-emu.yml b/.github/workflows/integration-emu.yml index 4f131ac26..a13c6a322 100644 --- a/.github/workflows/integration-emu.yml +++ b/.github/workflows/integration-emu.yml @@ -129,7 +129,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/integration-tdx.yml b/.github/workflows/integration-tdx.yml index 6466dc16a..0bc398c08 100644 --- a/.github/workflows/integration-tdx.yml +++ b/.github/workflows/integration-tdx.yml @@ -31,7 +31,7 @@ jobs: # - name: Install tools for sgx lib # run: sudo dnf group install 'Development Tools' | sudo dnf --enablerepo=powertools install ocaml ocaml-ocamlbuild wget rpm-build pkgcon - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit @@ -89,7 +89,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/library.yml b/.github/workflows/library.yml index 054f5dee9..71926b2ea 100644 --- a/.github/workflows/library.yml +++ b/.github/workflows/library.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 214040999..76dfaab33 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: build_type: [release, debug] steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index ee749066b..f643be850 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -8,7 +8,7 @@ jobs: security-events: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index f4d80b422..6c50bb231 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 73612f29f..4ddc17050 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -27,7 +27,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/weekly-cargo-update.yml b/.github/workflows/weekly-cargo-update.yml index 1df168235..2f6c7bfd3 100644 --- a/.github/workflows/weekly-cargo-update.yml +++ b/.github/workflows/weekly-cargo-update.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit diff --git a/.github/workflows/weekly-collateral-update.yml b/.github/workflows/weekly-collateral-update.yml index 3a9cad49b..ec606facd 100644 --- a/.github/workflows/weekly-collateral-update.yml +++ b/.github/workflows/weekly-collateral-update.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 with: egress-policy: audit From fa16cd58835638ed34a7df13ecf6920891898fa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 19:54:25 +0000 Subject: [PATCH 17/21] build(deps): bump actions/dependency-review-action from 4.9.0 to 5.0.0 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.9.0 to 5.0.0. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/2031cfc080254a8a887f58cffee85186f0e49e48...a1d282b36b6f3519aa1f3fc636f609c47dddb294) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 126fdecf9..f1549b650 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -24,4 +24,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 From e381895535e0e15330a4260606f29352b3179e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 19:54:29 +0000 Subject: [PATCH 18/21] build(deps): bump EmbarkStudios/cargo-deny-action from 2.0.17 to 2.0.18 Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 2.0.17 to 2.0.18. - [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases) - [Commits](https://github.com/embarkstudios/cargo-deny-action/compare/91bf2b620e09e18d6eb78b92e7861937469acedb...6c8f9facfa5047ec02d8485b6bf52b587b7777d1) --- updated-dependencies: - dependency-name: EmbarkStudios/cargo-deny-action dependency-version: 2.0.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deny.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 207a914cf..2e31bf1e1 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -34,6 +34,6 @@ jobs: with: submodules: recursive - run: bash sh_script/preparation.sh - - uses: EmbarkStudios/cargo-deny-action@91bf2b620e09e18d6eb78b92e7861937469acedb # v2.0.17 + - uses: EmbarkStudios/cargo-deny-action@6c8f9facfa5047ec02d8485b6bf52b587b7777d1 # v2.0.18 with: command: check ${{ matrix.checks }} \ No newline at end of file From 7b69136857403f71e4b02eb7db1245947890a98d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 19:56:49 +0000 Subject: [PATCH 19/21] build(deps): bump spin in /deps/td-shim-AzCVMEmu/azcvm-extract-report Bumps [spin](https://github.com/zesterer/spin-rs) from 0.10.0 to 0.11.0. - [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/zesterer/spin-rs/commits) --- updated-dependencies: - dependency-name: spin dependency-version: 0.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock | 6 +++--- deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock index 1ac8873a5..a8a98fa0f 100644 --- a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock +++ b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock @@ -1157,9 +1157,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +checksum = "783f3f6f6b01e295a669edfc402133a5f2553d1f0e81284b3ba4594e80bdd4a2" [[package]] name = "stable_deref_trait" @@ -1250,7 +1250,7 @@ dependencies = [ "lazy_static", "log", "sha2", - "spin 0.10.0", + "spin 0.11.0", "tdx-mock-data", "tdx-tdcall", "tokio", diff --git a/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml b/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml index b6a3034b0..da96f69a7 100644 --- a/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml +++ b/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml @@ -24,7 +24,7 @@ zerocopy = { version = "0.8", features = ["derive"] } # For compatibility with original tdx-tdcall interface bitflags = "2.11" -spin = { version = "0.10", default-features = false, features = ["spin_mutex"] } +spin = { version = "0.11", default-features = false, features = ["spin_mutex"] } lazy_static = { version = "1.4", features = ["spin_no_std"] } interrupt-emu = { path = "../interrupt-emu", package = "interrupt-emu" } From 6488bf4cbe158bac2b92e5909dc722d5e7c7ef79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 23:06:47 +0000 Subject: [PATCH 20/21] build(deps): bump spin in /deps/td-shim-AzCVMEmu/azcvm-extract-report Bumps spin from 0.11.0 to 0.12.0. --- updated-dependencies: - dependency-name: spin dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock | 6 +++--- deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock index a8a98fa0f..394f54ce8 100644 --- a/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock +++ b/deps/td-shim-AzCVMEmu/azcvm-extract-report/Cargo.lock @@ -1157,9 +1157,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783f3f6f6b01e295a669edfc402133a5f2553d1f0e81284b3ba4594e80bdd4a2" +checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865" [[package]] name = "stable_deref_trait" @@ -1250,7 +1250,7 @@ dependencies = [ "lazy_static", "log", "sha2", - "spin 0.11.0", + "spin 0.12.0", "tdx-mock-data", "tdx-tdcall", "tokio", diff --git a/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml b/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml index da96f69a7..297a742d0 100644 --- a/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml +++ b/deps/td-shim-AzCVMEmu/tdx-tdcall/Cargo.toml @@ -24,7 +24,7 @@ zerocopy = { version = "0.8", features = ["derive"] } # For compatibility with original tdx-tdcall interface bitflags = "2.11" -spin = { version = "0.11", default-features = false, features = ["spin_mutex"] } +spin = { version = "0.12", default-features = false, features = ["spin_mutex"] } lazy_static = { version = "1.4", features = ["spin_no_std"] } interrupt-emu = { path = "../interrupt-emu", package = "interrupt-emu" } From 30301afe71e5bfc2ffa89ecf31ac2303809911ac Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Wed, 20 May 2026 13:47:13 +0200 Subject: [PATCH 21/21] fix(migtd): fix code coverage collection for fuzzing tests Coverage collection via the -c flag was broken due to multiple issues: - AFL: RUSTFLAGS="-C instrument-coverage" was applied during cargo afl build/fuzz, conflicting with AFL's own instrumentation and fork server. Coverage profraw files were either corrupt or missing. Fix by building a separate non-AFL instrumented binary (without --features fuzz) after fuzzing, and replaying the AFL queue through it to generate valid coverage data. - AFL: grcov searched from "." which could pick up stale profdata files from other runs. Fix by writing profraw to a dedicated cov_profraw/ directory and pointing grcov only there. - AFL: the queue replay glob (queue/*) included the .state directory, causing panics. Fix by using find -type f to iterate only regular files. - Libfuzzer: grcov --binary-path pointed to fuzz/target/x86_64-unknown-linux-gnu/release/ (a file) but should be the release/ directory. Additionally, grcov searched from "." picking up incompatible profdata. Fix by pointing grcov to the specific fuzz/coverage//raw directory where cargo fuzz coverage writes profraw files. - Libfuzzer run_all_case: inverted conditional (if [ ! -d ... ]) meant the old coverage directory was never cleaned up. Fix the logic. Signed-off-by: Michal Tarnacki Co-authored-by: GitHub Copilot --- sh_script/fuzzing.sh | 46 ++++++++++++------- sh_script/unit_test_coverage.sh | 26 +++++------ src/std-support/rust-std-stub/Cargo.toml | 4 ++ src/std-support/rust-std-stub/src/io/error.rs | 3 +- src/std-support/rust-std-stub/src/io/impls.rs | 3 +- src/std-support/sys_time/src/lib.rs | 1 + 6 files changed, 50 insertions(+), 33 deletions(-) diff --git a/sh_script/fuzzing.sh b/sh_script/fuzzing.sh index 133ec9e8e..d79faa710 100644 --- a/sh_script/fuzzing.sh +++ b/sh_script/fuzzing.sh @@ -139,11 +139,6 @@ run_single_case() { echo $test_case | grep "^afl" if [ "$?" == "0" ];then echo "The test method is afl" - if [ "${collect_coverage}" == "YES" ]; then - export RUSTFLAGS="-C instrument-coverage" - export LLVM_PROFILE_FILE="fuzz-%p-%m.profraw" - find . -name "*.profraw" | xargs rm -rf - fi cargo_build=`cargo afl build --manifest-path fuzz/Cargo.toml --bin $test_case --features fuzz --no-default-features` if [ "$?" != "0" ];then @@ -174,8 +169,20 @@ run_single_case() { if [ "${collect_coverage}" == "YES" ]; then [ -d "${test_case}_cov" ] && rm -rf "${test_case}_cov" + rm -rf cov_profraw && mkdir -p cov_profraw + + # Build without AFL runtime for coverage replay + RUSTFLAGS="-C instrument-coverage" cargo build --manifest-path fuzz/Cargo.toml --bin $test_case --no-default-features + export LLVM_PROFILE_FILE="cov_profraw/fuzz-%p-%m.profraw" + + # Replay the AFL queue through the instrumented binary + find fuzz/artifacts/$test_case/default/queue -maxdepth 1 -type f | while read input; do + fuzz/target/debug/$test_case "$input" || true + done + unset LLVM_PROFILE_FILE - grcov . -s src --binary-path fuzz/target/debug/$test_case -t html --branch --ignore-not-existing -o "${test_case}_cov" + grcov cov_profraw -s src --binary-path fuzz/target/debug/ -t html --branch --ignore-not-existing -o "${test_case}_cov" + rm -rf cov_profraw fi fi popd @@ -208,7 +215,7 @@ run_single_case() { find . -name "*.profraw" | xargs rm -rf cargo fuzz coverage $test_case - grcov . -s src -b fuzz/target/x86_64-unknown-linux-gnu/release/$test_case -t html --branch --ignore-not-existing -o "${test_case}_fuzz_cov" + grcov fuzz/coverage/$test_case/raw -s src -b fuzz/target/x86_64-unknown-linux-gnu/release/ -t html --branch --ignore-not-existing -o "${test_case}_fuzz_cov" fi popd fi @@ -236,12 +243,12 @@ run_all_case(){ timeout $test_time cargo fuzz run $fuzz if [ "${collect_coverage}" == "YES" ]; then - if [ ! -d "${fuzz}_fuzz_cov" ]; then + if [ -d "${fuzz}_fuzz_cov" ]; then rm -rf ${fuzz}_fuzz_cov fi find . -name "*.profraw" | xargs rm -rf cargo fuzz coverage $fuzz - grcov . -s src -b fuzz/target/x86_64-unknown-linux-gnu/release/$fuzz -t html --branch --ignore-not-existing -o "${fuzz}_fuzz_cov" + grcov fuzz/coverage/$fuzz/raw -s src -b fuzz/target/x86_64-unknown-linux-gnu/release/ -t html --branch --ignore-not-existing -o "${fuzz}_fuzz_cov" fi done popd @@ -252,12 +259,6 @@ run_all_case(){ fuzz_list=$(cargo fuzz list) for fuzz in $fuzz_list;do if [[ "$fuzz" =~ "afl" ]];then - if [ "${collect_coverage}" == "YES" ]; then - export RUSTFLAGS="-C instrument-coverage" - export LLVM_PROFILE_FILE="fuzz-%p-%m.profraw" - find . -name "*.profraw" | xargs rm -rf - fi - cargo_build=`cargo afl build --manifest-path fuzz/Cargo.toml --bin $fuzz --features fuzz --no-default-features` if [ "$?" != "0" ];then echo "Error: Build execution failed" @@ -279,7 +280,20 @@ run_all_case(){ if [ -d "${fuzz}_cov" ]; then rm -rf "${fuzz}_cov" fi - grcov . -s src --binary-path fuzz/target/debug/$fuzz -t html --branch --ignore-not-existing -o "${fuzz}_cov" + rm -rf cov_profraw && mkdir -p cov_profraw + + # Build without AFL runtime for coverage replay + RUSTFLAGS="-C instrument-coverage" cargo build --manifest-path fuzz/Cargo.toml --bin $fuzz --no-default-features + export LLVM_PROFILE_FILE="cov_profraw/fuzz-%p-%m.profraw" + + # Replay the AFL queue through the instrumented binary + find fuzz/artifacts/$fuzz/default/queue -maxdepth 1 -type f | while read input; do + fuzz/target/debug/$fuzz "$input" || true + done + unset LLVM_PROFILE_FILE + + grcov cov_profraw -s src --binary-path fuzz/target/debug/ -t html --branch --ignore-not-existing -o "${fuzz}_cov" + rm -rf cov_profraw fi fi else diff --git a/sh_script/unit_test_coverage.sh b/sh_script/unit_test_coverage.sh index 646373567..47133b697 100644 --- a/sh_script/unit_test_coverage.sh +++ b/sh_script/unit_test_coverage.sh @@ -1,26 +1,22 @@ #!/bin/bash -if [[ ! $PWD =~ firmware.security.tdx.migtd.td$ ]];then - pushd .. -fi - -unittest_folders=( - "policy" - "migtd" -) +# Navigate to repo root if run from sh_script/ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$REPO_ROOT" export RUSTFLAGS="-Cinstrument-coverage" export LLVM_PROFILE_FILE="unittest-%p-%m.profraw" -find . -name "*.profraw" | xargs rm -rf +find . -name "*.profraw" -not -path "./deps/*" -delete -for path in ${unittest_folders[@]}; do - pushd $path - cargo test - popd -done +cargo test -p policy -p migtd -p crypto -p virtio -p vsock -grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing -o unit_test_coverage +grcov . --binary-path ./target/debug/ -s . -t html --branch \ + --ignore-not-existing \ + --ignore "deps/*" \ + --ignore "target/*" \ + -o unit_test_coverage unset RUSTFLAGS unset LLVM_PROFILE_FILE diff --git a/src/std-support/rust-std-stub/Cargo.toml b/src/std-support/rust-std-stub/Cargo.toml index 96a7fcb20..9f25bea8e 100644 --- a/src/std-support/rust-std-stub/Cargo.toml +++ b/src/std-support/rust-std-stub/Cargo.toml @@ -8,6 +8,10 @@ license = "MIT" description = "for rustls no_std use, export rustls used functions, traits and structs in std" readme = "readme.md" +[features] +# Enable to run tests copied from std (requires nightly + full std environment) +std_tests = [] + [dependencies] sys_time = {path = "../sys_time"} diff --git a/src/std-support/rust-std-stub/src/io/error.rs b/src/std-support/rust-std-stub/src/io/error.rs index 3f54de856..f68998a6e 100644 --- a/src/std-support/rust-std-stub/src/io/error.rs +++ b/src/std-support/rust-std-stub/src/io/error.rs @@ -1,4 +1,5 @@ -#[cfg(test)] +// Tests require std features (format!, sys::decode_error_kind) unavailable in this no_std stub +#[cfg(all(test, feature = "std_tests"))] mod tests; use crate::error; diff --git a/src/std-support/rust-std-stub/src/io/impls.rs b/src/std-support/rust-std-stub/src/io/impls.rs index cf8a36a4c..cbe2009ba 100644 --- a/src/std-support/rust-std-stub/src/io/impls.rs +++ b/src/std-support/rust-std-stub/src/io/impls.rs @@ -1,4 +1,5 @@ -#[cfg(test)] +// Tests require #[bench] (unstable) and std prelude unavailable in this no_std stub +#[cfg(all(test, feature = "std_tests"))] mod tests; use crate::io::{self, Error, ErrorKind, Initializer, IoSlice, IoSliceMut, Read, Write}; diff --git a/src/std-support/sys_time/src/lib.rs b/src/std-support/sys_time/src/lib.rs index 944d1cdc2..f8bdda275 100644 --- a/src/std-support/sys_time/src/lib.rs +++ b/src/std-support/sys_time/src/lib.rs @@ -31,6 +31,7 @@ pub fn get_sys_time() -> Option { mod tests { use super::get_sys_time; #[test] + #[ignore = "requires x86 I/O port access (CMOS/RTC), segfaults in userspace"] fn it_works() { assert_ne!(get_sys_time().unwrap(), 0); }