From 3c3b39815739a5a98db8477b4a923e8f699c7147 Mon Sep 17 00:00:00 2001 From: Haitao Huang Date: Thu, 14 May 2026 02:05:43 +0000 Subject: [PATCH] fix(attest): increase attestation heap and add init_td_info parsing - Raise C-lib heap from 512 KiB to 2 MiB for loopback migration (4 verify_quote calls exhaust the old heap due to fragmentation) - Add init_td_info field and read_from_bytes for policy_v2 wire format Signed-off-by: Haitao Huang --- Cargo.lock | 1 + src/attestation/src/attest.rs | 11 +++- src/migtd/src/migration/mod.rs | 111 ++++++++++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 759a0b76..bfe5ddba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2240,6 +2240,7 @@ dependencies = [ "rustls-webpki", "serde", "serde_json", + "spin 0.10.0", "spin 0.9.8", "sys_time 0.1.0", "untrusted", diff --git a/src/attestation/src/attest.rs b/src/attestation/src/attest.rs index 38e841a2..2af89ea7 100644 --- a/src/attestation/src/attest.rs +++ b/src/attestation/src/attest.rs @@ -27,7 +27,16 @@ compile_error!( pub const TD_QUOTE_SIZE: usize = 0x2000; const TD_REPORT_VERIFY_SIZE: usize = 1024; -const ATTEST_HEAP_SIZE: usize = 0x80000; +// NOTE: bumped from 0x80000 (512 KiB) to 0x200000 (2 MiB). +// Each verify_quote_integrity_ex() call allocates from this private heap and +// the dlmalloc heap does not move sbrk down (limited defragmentation after +// each free() calls) to original position after the call returns. With +// LOCAL_TCB_INFO caching removed (commit 3c44ea9), each migration now does +// up to 4 verify_quote calls in the same MigTD process (loopback), which can +// exhaust the 512 KiB heap at 3rd call due to statics and fragmentation in +// heap, and trigger an internal abort (#UD) inside the C verifier lib with +// no error code returned. +const ATTEST_HEAP_SIZE: usize = 0x200000; /// C-compatible version of Collateral with null-terminated strings #[derive(Debug)] diff --git a/src/migtd/src/migration/mod.rs b/src/migtd/src/migration/mod.rs index e612b49c..8d6e715b 100644 --- a/src/migtd/src/migration/mod.rs +++ b/src/migtd/src/migration/mod.rs @@ -145,8 +145,29 @@ pub const STREAM_SOCKET_INFO_HOB_GUID: Guid = Guid::from_fields( &[0x2f, 0x3d, 0x2e, 0x8b, 0x1e, 0xe], ); +/// Size in bytes of the TDX-module `TdInfo` struct (TDINFO_STRUCT per the +/// TDX module ABI and GHCI 1.5 MIGTD_DATA `initMigtdData`). +pub const TD_INFO_SIZE: usize = core::mem::size_of::(); + +// Compile-time guard: catch any drift in the upstream TdInfo layout. +const _: () = { + assert!(TD_INFO_SIZE == 512); +}; + +/// Size of the fixed `MigtdMigrationInformation` header preceding the +/// optional `init_td_info` tail (per GHCI 1.5 StartMigration layout). +#[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] +const MIGTD_MIGRATION_INFO_HEADER_SIZE: usize = { + // u64(8) + u8(1) + u8(1) + [u8;6] + [u64;4](32) + u64(8) + 8 + 1 + 1 + 6 + 32 + 8 +}; + #[repr(C)] -#[derive(Debug, Pread, Pwrite, Clone, Default)] +#[derive(Debug, Pread, Pwrite, Clone)] +#[cfg_attr( + not(all(feature = "vmcall-raw", feature = "policy_v2")), + derive(Default) +)] pub struct MigtdMigrationInformation { // ID for the migration request, which can be used in TDG.VP.VMCALL // @@ -174,9 +195,95 @@ pub struct MigtdMigrationInformation { // Unique identifier for the communication between MigTD and VMM // It can be retrieved from MIGTD_STREAM_SOCKET_INFO HOB pub communication_id: u64, + + // Per GHCI 1.5: optional 512-byte TDINFO_STRUCT of the initial MigTD. + // Present when `has_init_data == 1`. Use `init_td_info_if_present()` for + // safe access. Sent by the VMM as the raw `TdInfo` bytes (no envelope). + // + // NOTE: literal `512` (== TD_INFO_SIZE) is required here because + // scroll-derive 0.10.5 only accepts integer literals as array length. + // A compile-time assertion above guards against drift. + #[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] + pub init_td_info: [u8; 512], } -#[cfg(feature = "vmcall-raw")] +#[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] +impl Default for MigtdMigrationInformation { + fn default() -> Self { + Self { + mig_request_id: 0, + migration_source: 0, + has_init_data: 0, + _reserved: [0; 6], + target_td_uuid: [0; 4], + binding_handle: 0, + init_td_info: [0u8; TD_INFO_SIZE], + } + } +} + +#[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] +impl MigtdMigrationInformation { + /// Safe accessor for the optional initial TDINFO_STRUCT. Returns `Some` + /// only when the VMM signaled `has_init_data == 1`. + pub fn init_td_info_if_present(&self) -> Option<&[u8; TD_INFO_SIZE]> { + if self.has_init_data == 1 { + Some(&self.init_td_info) + } else { + None + } + } +} + +#[cfg(all(feature = "vmcall-raw", feature = "policy_v2"))] +impl MigtdMigrationInformation { + /// Parse a vmcall-raw + policy_v2 wire payload of either: + /// - 56 bytes (short form, no `init_td_info` tail, `has_init_data == 0`), or + /// - 568 bytes (full form, with `init_td_info` tail, `has_init_data == 1`). + /// Used for both StartMigration and StartRebinding (the wire layouts are + /// identical; byte 8 encodes whether this MigTD is the source/initiator). + pub fn read_from_bytes( + data_length: u32, + payload: &[u8], + ) -> core::result::Result { + let short_size = MIGTD_MIGRATION_INFO_HEADER_SIZE as u32; + let full_size = core::mem::size_of::() as u32; + if data_length != short_size && data_length != full_size { + return Err(MigrationResult::InvalidParameter); + } + if (payload.len() as u32) < data_length { + return Err(MigrationResult::InvalidParameter); + } + let parsed: Self = if data_length == full_size { + payload + .pread(0) + .map_err(|_| MigrationResult::InvalidParameter)? + } else { + // Short form: zero-pad to full_size and pread; tail field's default + // is `[0u8; TD_INFO_SIZE]` so zero-fill is correct. + let mut padded = alloc::vec![0u8; full_size as usize]; + padded[..short_size as usize].copy_from_slice(&payload[..short_size as usize]); + padded + .as_slice() + .pread(0) + .map_err(|_| MigrationResult::InvalidParameter)? + }; + + if parsed._reserved != [0; 6] { + return Err(MigrationResult::InvalidParameter); + } + if parsed.has_init_data > 1 { + return Err(MigrationResult::InvalidParameter); + } + let has_optional = data_length == full_size; + if (parsed.has_init_data == 1) != has_optional { + return Err(MigrationResult::InvalidParameter); + } + Ok(parsed) + } +} + +#[cfg(all(feature = "vmcall-raw", not(feature = "policy_v2")))] impl MigtdMigrationInformation { pub fn read_from_bytes( data_length: u32,