Skip to content

Commit 9923b94

Browse files
haitaohuangCopilot
authored andcommitted
fix(migtd/migration): tighten MigtdMigrationInformation parser check
The policy_v2 length check accepted a full-size payload with has_init_data == 0 — a band-aid for a test whose fixture started writing the full struct after the buffer-unification refactor grew its size. Replace it with a symmetric check that rejects whenever the flag and length disagree: (has_init_data == 1) != (data_length == full_size) => reject Move the 8 parser tests from rebinding.rs (only built under main+policy_v2+vmcall-raw, never run by `xtask lib-test`) to a policy_v2-gated module in mod.rs. The shared build_mig_info fixture is now layout-aware so the same tests cover both vmcall-raw (56/568) and non-vmcall-raw (72/584) struct layouts. Make MIGTD_MIGRATION_INFO_HEADER_SIZE pub(crate) and update two fixtures to emit short-form payloads that satisfy the new check: data::create_mig_info_hob (policy_v2 only) and session::build_migration_payload. The production HOB consumer create_migration_information stays layout-neutral; consistency is now enforced inside the parser. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
1 parent 8df57dd commit 9923b94

4 files changed

Lines changed: 163 additions & 129 deletions

File tree

src/migtd/src/migration/data.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -725,9 +725,19 @@ mod test {
725725
}
726726

727727
fn create_mig_info_hob(hob: &mut [u8], offset: &mut usize) {
728+
// Under policy_v2, build a short-form HOB payload (header only,
729+
// has_init_data = 0). The wire format also supports a full-size form
730+
// carrying init_td_info when has_init_data == 1; that variant is
731+
// exercised by the parser-level tests in `mod.rs`. Under non-policy_v2
732+
// builds, MigtdMigrationInformation has no init_td_info field and the
733+
// only valid form is the full struct.
734+
#[cfg(feature = "policy_v2")]
735+
let payload_size = MIGTD_MIGRATION_INFO_HEADER_SIZE;
736+
#[cfg(not(feature = "policy_v2"))]
737+
let payload_size = size_of::<MigtdMigrationInformation>();
738+
728739
let mig_info_hob_guid = MIGRATION_INFORMATION_HOB_GUID.as_bytes();
729-
let mig_info_hob =
730-
create_guid_hob(mig_info_hob_guid, size_of::<MigtdMigrationInformation>());
740+
let mig_info_hob = create_guid_hob(mig_info_hob_guid, payload_size);
731741
let mig_info = MigtdMigrationInformation {
732742
mig_request_id: 0,
733743
migration_source: 1,
@@ -742,8 +752,12 @@ mod test {
742752
};
743753
hob.pwrite(mig_info_hob, *offset).unwrap();
744754
*offset += size_of::<GuidExtension>();
745-
hob.pwrite(mig_info, *offset).unwrap();
746-
*offset += size_of::<MigtdMigrationInformation>();
755+
// Serialize the full struct to a tmp buffer, then copy only the
756+
// header bytes — the tail init_td_info is omitted in the short form.
757+
let mut tmp = [0u8; size_of::<MigtdMigrationInformation>()];
758+
tmp.pwrite(mig_info, 0).unwrap();
759+
hob[*offset..*offset + payload_size].copy_from_slice(&tmp[..payload_size]);
760+
*offset += payload_size;
747761
}
748762

749763
fn create_socket_info_hob(hob: &mut [u8], offset: &mut usize) {

src/migtd/src/migration/mod.rs

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ pub fn local_init_td_info() -> Result<[u8; TD_INFO_SIZE], MigrationResult> {
193193
/// Size of the fixed `MigtdMigrationInformation` header preceding the
194194
/// optional `init_td_info` tail (per GHCI 1.5 StartMigration layout).
195195
#[cfg(feature = "policy_v2")]
196-
const MIGTD_MIGRATION_INFO_HEADER_SIZE: usize =
196+
pub(crate) const MIGTD_MIGRATION_INFO_HEADER_SIZE: usize =
197197
core::mem::size_of::<MigtdMigrationInformation>() - TD_INFO_SIZE;
198198

199199
#[repr(C)]
@@ -309,9 +309,9 @@ impl MigtdMigrationInformation {
309309
if parsed.has_init_data > 1 {
310310
return Err(MigrationResult::InvalidParameter);
311311
}
312-
// has_init_data == 1 requires full-size payload; full-size payload with
313-
// has_init_data == 0 is accepted (e.g. HOBs always serialize full struct).
314-
if parsed.has_init_data == 1 && data_length != full_size {
312+
// has_init_data and data_length must be consistent: the full-size
313+
// payload is required iff has_init_data == 1.
314+
if (parsed.has_init_data == 1) != (data_length == full_size) {
315315
return Err(MigrationResult::InvalidParameter);
316316
}
317317
Ok(parsed)
@@ -548,3 +548,136 @@ impl From<TimeoutError> for MigrationResult {
548548
MigrationResult::NetworkError
549549
}
550550
}
551+
552+
#[cfg(test)]
553+
#[cfg(feature = "policy_v2")]
554+
mod test {
555+
use super::*;
556+
use alloc::vec;
557+
558+
/// Create a 512-byte TDINFO_STRUCT with known mrowner and mrownerconfig.
559+
fn make_tdinfo(mrowner: &[u8; 48], mrownerconfig: &[u8; 48]) -> [u8; TD_INFO_SIZE] {
560+
let mut tdinfo = [0u8; TD_INFO_SIZE];
561+
// mrowner at offset 112..160
562+
tdinfo[112..160].copy_from_slice(mrowner);
563+
// mrownerconfig at offset 160..208
564+
tdinfo[160..208].copy_from_slice(mrownerconfig);
565+
tdinfo
566+
}
567+
568+
/// Build a MigtdMigrationInformation byte buffer matching the active
569+
/// feature layout. Under non-vmcall-raw, `mig_policy_id` and
570+
/// `communication_id` (zero-initialized) are inserted after
571+
/// `binding_handle`. When `init_tdinfo` is `Some`, the raw TDINFO bytes
572+
/// are appended directly (no envelope, per the unified wire contract).
573+
fn build_mig_info(
574+
mig_request_id: u64,
575+
migration_source: u8,
576+
has_init_data: u8,
577+
uuid: [u64; 4],
578+
binding_handle: u64,
579+
init_tdinfo: Option<&[u8]>,
580+
) -> Vec<u8> {
581+
let mut buf = Vec::new();
582+
buf.extend_from_slice(&mig_request_id.to_le_bytes()); // 0..8
583+
buf.push(migration_source); // 8
584+
buf.push(has_init_data); // 9
585+
buf.extend_from_slice(&[0u8; 6]); // 10..16 reserved
586+
for u in &uuid {
587+
buf.extend_from_slice(&u.to_le_bytes()); // 16..48
588+
}
589+
buf.extend_from_slice(&binding_handle.to_le_bytes()); // 48..56
590+
#[cfg(not(feature = "vmcall-raw"))]
591+
{
592+
buf.extend_from_slice(&0u64.to_le_bytes()); // mig_policy_id 56..64
593+
buf.extend_from_slice(&0u64.to_le_bytes()); // communication_id 64..72
594+
}
595+
if let Some(data) = init_tdinfo {
596+
buf.extend_from_slice(data);
597+
}
598+
buf
599+
}
600+
601+
#[test]
602+
fn test_mig_info_no_init_data() {
603+
let buf = build_mig_info(42, 1, 0, [1, 2, 3, 4], 99, None);
604+
let info = MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf)
605+
.expect("should parse");
606+
assert_eq!(info.mig_request_id, 42);
607+
assert_eq!(info.migration_source, 1);
608+
assert_eq!(info.has_init_data, 0);
609+
assert_eq!(info.target_td_uuid, [1, 2, 3, 4]);
610+
assert_eq!(info.binding_handle, 99);
611+
assert!(info.init_td_info_if_present().is_none());
612+
// Padded tail is zero.
613+
assert_eq!(info.init_td_info, [0u8; TD_INFO_SIZE]);
614+
}
615+
616+
#[test]
617+
fn test_mig_info_with_init_data() {
618+
let tdinfo = make_tdinfo(&[0xCAu8; 48], &[0xFEu8; 48]);
619+
let buf = build_mig_info(7, 0, 1, [10, 20, 30, 40], 55, Some(&tdinfo));
620+
let info = MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf)
621+
.expect("should parse with init data");
622+
assert_eq!(info.mig_request_id, 7);
623+
assert_eq!(info.has_init_data, 1);
624+
let init = info.init_td_info_if_present().expect("should be present");
625+
assert_eq!(init, &tdinfo);
626+
assert_eq!(td_info_mrowner(init), &[0xCAu8; 48]);
627+
assert_eq!(td_info_mrownerconfig(init), &[0xFEu8; 48]);
628+
}
629+
630+
#[test]
631+
fn test_mig_info_rejects_short_buffer() {
632+
// Anything shorter than the layout's short-form header must be rejected.
633+
let too_short = MIGTD_MIGRATION_INFO_HEADER_SIZE - 1;
634+
assert!(MigtdMigrationInformation::read_from_bytes(10, &[0u8; 10]).is_err());
635+
assert!(MigtdMigrationInformation::read_from_bytes(
636+
too_short as u32,
637+
&vec![0u8; too_short]
638+
)
639+
.is_err());
640+
}
641+
642+
#[test]
643+
fn test_mig_info_rejects_nonzero_reserved() {
644+
let mut buf = build_mig_info(1, 0, 0, [0; 4], 0, None);
645+
buf[10] = 0xFF; // reserved byte not zero
646+
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
647+
}
648+
649+
#[test]
650+
fn test_mig_info_rejects_has_init_data_without_tail() {
651+
// has_init_data=1 but no tail bytes following → flag/length mismatch
652+
let buf = build_mig_info(1, 0, 1, [0; 4], 0, None);
653+
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
654+
}
655+
656+
#[test]
657+
fn test_mig_info_rejects_full_form_without_flag() {
658+
// has_init_data=0 but full-size buffer (with init_td_info tail) → flag/length mismatch
659+
let tdinfo = [0u8; TD_INFO_SIZE];
660+
let buf = build_mig_info(1, 0, 0, [0; 4], 0, Some(&tdinfo));
661+
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
662+
}
663+
664+
#[test]
665+
fn test_mig_info_rejects_invalid_has_init_data() {
666+
// has_init_data must be 0 or 1
667+
let mut buf = build_mig_info(1, 0, 0, [0; 4], 0, None);
668+
buf[9] = 2;
669+
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
670+
buf[9] = 0xFF;
671+
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
672+
}
673+
674+
#[test]
675+
fn test_mig_info_rejects_unexpected_length() {
676+
// Only short or full lengths are accepted; mid-sized buffers are rejected.
677+
let mid = MIGTD_MIGRATION_INFO_HEADER_SIZE + 1;
678+
let mid_buf = vec![0u8; mid];
679+
assert!(
680+
MigtdMigrationInformation::read_from_bytes(mid_buf.len() as u32, &mid_buf).is_err()
681+
);
682+
}
683+
}

src/migtd/src/migration/rebinding.rs

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -522,123 +522,3 @@ async fn tls_session_read_exact(
522522
}
523523
Ok(())
524524
}
525-
526-
#[cfg(test)]
527-
mod test {
528-
use super::*;
529-
use crate::migration::{td_info_mrowner, td_info_mrownerconfig};
530-
use alloc::vec;
531-
532-
/// Create a 512-byte TDINFO_STRUCT with known mrowner and mrownerconfig.
533-
fn make_tdinfo(mrowner: &[u8; 48], mrownerconfig: &[u8; 48]) -> [u8; TD_INFO_SIZE] {
534-
let mut tdinfo = [0u8; TD_INFO_SIZE];
535-
// mrowner at offset 112..160
536-
tdinfo[112..160].copy_from_slice(mrowner);
537-
// mrownerconfig at offset 160..208
538-
tdinfo[160..208].copy_from_slice(mrownerconfig);
539-
tdinfo
540-
}
541-
542-
/// Build a MigtdMigrationInformation byte buffer. When `init_tdinfo` is `Some`, the
543-
/// raw TDINFO bytes are appended directly (no envelope, per the new wire
544-
/// contract).
545-
fn build_mig_info(
546-
mig_request_id: u64,
547-
migration_source: u8,
548-
has_init_data: u8,
549-
uuid: [u64; 4],
550-
binding_handle: u64,
551-
init_tdinfo: Option<&[u8]>,
552-
) -> Vec<u8> {
553-
let mut buf = Vec::new();
554-
buf.extend_from_slice(&mig_request_id.to_le_bytes()); // 0..8
555-
buf.push(migration_source); // 8
556-
buf.push(has_init_data); // 9
557-
buf.extend_from_slice(&[0u8; 6]); // 10..16 reserved
558-
for u in &uuid {
559-
buf.extend_from_slice(&u.to_le_bytes()); // 16..48
560-
}
561-
buf.extend_from_slice(&binding_handle.to_le_bytes()); // 48..56
562-
if let Some(data) = init_tdinfo {
563-
buf.extend_from_slice(data); // 56..56+512
564-
}
565-
buf
566-
}
567-
568-
#[test]
569-
fn test_mig_info_no_init_data() {
570-
let buf = build_mig_info(42, 1, 0, [1, 2, 3, 4], 99, None);
571-
let info = MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf)
572-
.expect("should parse");
573-
assert_eq!(info.mig_request_id, 42);
574-
assert_eq!(info.migration_source, 1);
575-
assert_eq!(info.has_init_data, 0);
576-
assert_eq!(info.target_td_uuid, [1, 2, 3, 4]);
577-
assert_eq!(info.binding_handle, 99);
578-
assert!(info.init_td_info_if_present().is_none());
579-
// Padded tail is zero.
580-
assert_eq!(info.init_td_info, [0u8; TD_INFO_SIZE]);
581-
}
582-
583-
#[test]
584-
fn test_mig_info_with_init_data() {
585-
let tdinfo = make_tdinfo(&[0xCAu8; 48], &[0xFEu8; 48]);
586-
let buf = build_mig_info(7, 0, 1, [10, 20, 30, 40], 55, Some(&tdinfo));
587-
let info = MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf)
588-
.expect("should parse with init data");
589-
assert_eq!(info.mig_request_id, 7);
590-
assert_eq!(info.has_init_data, 1);
591-
let init = info.init_td_info_if_present().expect("should be present");
592-
assert_eq!(init, &tdinfo);
593-
assert_eq!(td_info_mrowner(init), &[0xCAu8; 48]);
594-
assert_eq!(td_info_mrownerconfig(init), &[0xFEu8; 48]);
595-
}
596-
597-
#[test]
598-
fn test_mig_info_rejects_short_buffer() {
599-
assert!(MigtdMigrationInformation::read_from_bytes(10, &[0u8; 10]).is_err());
600-
assert!(MigtdMigrationInformation::read_from_bytes(55, &[0u8; 55]).is_err());
601-
// 55 < 56
602-
}
603-
604-
#[test]
605-
fn test_mig_info_rejects_nonzero_reserved() {
606-
let mut buf = build_mig_info(1, 0, 0, [0; 4], 0, None);
607-
buf[10] = 0xFF; // reserved byte not zero
608-
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
609-
}
610-
611-
#[test]
612-
fn test_mig_info_rejects_has_init_data_without_tail() {
613-
// has_init_data=1 but no tail bytes following → flag/length mismatch
614-
let buf = build_mig_info(1, 0, 1, [0; 4], 0, None);
615-
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
616-
}
617-
618-
#[test]
619-
fn test_mig_info_rejects_full_form_without_flag() {
620-
// has_init_data=0 but full 568-byte buffer → flag/length mismatch
621-
let tdinfo = [0u8; TD_INFO_SIZE];
622-
let buf = build_mig_info(1, 0, 0, [0; 4], 0, Some(&tdinfo));
623-
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
624-
}
625-
626-
#[test]
627-
fn test_mig_info_rejects_invalid_has_init_data() {
628-
// has_init_data must be 0 or 1
629-
let mut buf = build_mig_info(1, 0, 0, [0; 4], 0, None);
630-
buf[9] = 2;
631-
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
632-
buf[9] = 0xFF;
633-
assert!(MigtdMigrationInformation::read_from_bytes(buf.len() as u32, &buf).is_err());
634-
}
635-
636-
#[test]
637-
fn test_mig_info_rejects_unexpected_length() {
638-
// Only short (56) or full (568) lengths are accepted.
639-
let mid_buf = vec![0u8; 100];
640-
assert!(
641-
MigtdMigrationInformation::read_from_bytes(mid_buf.len() as u32, &mid_buf).is_err()
642-
);
643-
}
644-
}

src/migtd/src/migration/session.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,8 @@ mod test {
13601360
#[cfg(feature = "vmcall-raw")]
13611361
mod parse_request_tests {
13621362
use super::super::{parse_request, REQUESTS};
1363+
#[cfg(feature = "policy_v2")]
1364+
use crate::migration::MIGTD_MIGRATION_INFO_HEADER_SIZE;
13631365
use crate::migration::{
13641366
data::{RequestDataBufferHeader, WaitForRequestResponse},
13651367
EnableLogAreaInfo, MigrationResult, MigtdMigrationInformation, ReportInfo,
@@ -1391,7 +1393,12 @@ mod test {
13911393
}
13921394

13931395
fn build_migration_payload(request_id: u64, is_source: u8) -> Vec<u8> {
1394-
let mut payload = vec![0u8; size_of::<MigtdMigrationInformation>()];
1396+
// Short-form payload: header only, has_init_data = 0 (init_td_info absent).
1397+
#[cfg(feature = "policy_v2")]
1398+
let size = MIGTD_MIGRATION_INFO_HEADER_SIZE;
1399+
#[cfg(not(feature = "policy_v2"))]
1400+
let size = size_of::<MigtdMigrationInformation>();
1401+
let mut payload = vec![0u8; size];
13951402
payload[0..8].copy_from_slice(&request_id.to_le_bytes());
13961403
payload[8] = is_source;
13971404
payload

0 commit comments

Comments
 (0)