Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src/attestation/src/attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Comment on lines +30 to 40
/// C-compatible version of Collateral with null-terminated strings
#[derive(Debug)]
Expand Down
111 changes: 109 additions & 2 deletions src/migtd/src/migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<tdx_tdcall::tdreport::TdInfo>();

// 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
// <Service.MigTD.ReportStatus>
Expand Down Expand Up @@ -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],
}
Comment on lines +210 to +221
}
}

#[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<Self, MigrationResult> {
let short_size = MIGTD_MIGRATION_INFO_HEADER_SIZE as u32;
let full_size = core::mem::size_of::<Self>() as u32;
if data_length != short_size && data_length != full_size {
return Err(MigrationResult::InvalidParameter);
}
Comment on lines +245 to +253
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,
Expand Down
Loading