Skip to content

Commit e2ba5e7

Browse files
test(rebinding): add unit tests for InitData and RebindingInfo
Add unit tests for updated data structures and verification: - rebinding.rs: tests for InitData (roundtrip, mrowner, bad signature, bad version, multiple entries, wrong type, short tdinfo, empty input) and RebindingInfo (no init, with init, short buffer, nonzero reserved, missing init data) - mig_policy.rs: tests for verify_servtd_hash (valid, wrong hash, short input, ignore_attributes, ignore_mrowner) and get_rtmrs_from_tdinfo Co-authored-by: Grams, Stanislaw <stanislaw.grams@intel.com>
1 parent dfba733 commit e2ba5e7

2 files changed

Lines changed: 301 additions & 1 deletion

File tree

src/migtd/src/mig_policy.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,121 @@ mod v2 {
705705
let iso_date = unix_to_iso8601(timestamp).unwrap();
706706
assert_eq!(iso_date, "2024-01-01T00:00:00Z");
707707
}
708+
709+
#[test]
710+
fn test_verify_servtd_hash_valid() {
711+
// Build a 512-byte TDINFO_STRUCT with known content
712+
let mut tdinfo_bytes = [0u8; 512];
713+
tdinfo_bytes[0..8].copy_from_slice(&[0x01; 8]); // attributes
714+
tdinfo_bytes[8..16].copy_from_slice(&[0x02; 8]); // xfam
715+
716+
// Compute expected hash: SHA384(SHA384(tdinfo) || type(u16) || attr(u64))
717+
let servtd_attr: u64 = 0;
718+
let info_hash = digest_sha384(&tdinfo_bytes).unwrap();
719+
let mut buffer = [0u8; SHA384_DIGEST_SIZE + size_of::<u16>() + size_of::<u64>()];
720+
buffer[..SHA384_DIGEST_SIZE].copy_from_slice(&info_hash);
721+
buffer[SHA384_DIGEST_SIZE..SHA384_DIGEST_SIZE + 2]
722+
.copy_from_slice(&SERVTD_TYPE_MIGTD.to_le_bytes());
723+
buffer[SHA384_DIGEST_SIZE + 2..SHA384_DIGEST_SIZE + 10]
724+
.copy_from_slice(&servtd_attr.to_le_bytes());
725+
let expected_hash = digest_sha384(&buffer).unwrap();
726+
727+
let result = verify_servtd_hash(&tdinfo_bytes, servtd_attr, &expected_hash);
728+
assert!(result.is_ok());
729+
let td_info = result.unwrap();
730+
assert_eq!(td_info.attributes, [0x01; 8]);
731+
assert_eq!(td_info.xfam, [0x02; 8]);
732+
}
733+
734+
#[test]
735+
fn test_verify_servtd_hash_wrong_hash() {
736+
let tdinfo_bytes = [0u8; 512];
737+
let wrong_hash = [0xFFu8; 48];
738+
let result = verify_servtd_hash(&tdinfo_bytes, 0, &wrong_hash);
739+
assert!(result.is_err());
740+
}
741+
742+
#[test]
743+
fn test_verify_servtd_hash_short_input() {
744+
let short = [0u8; 256]; // too small for TdInfo (512 bytes)
745+
let result = verify_servtd_hash(&short, 0, &[0u8; 48]);
746+
assert!(matches!(result, Err(PolicyError::InvalidParameter)));
747+
}
748+
749+
#[test]
750+
fn test_verify_servtd_hash_with_ignore_attributes() {
751+
// Build TdInfo with non-zero attributes
752+
let mut tdinfo_bytes = [0u8; 512];
753+
tdinfo_bytes[0..8].copy_from_slice(&[0xFF; 8]); // attributes
754+
755+
// Compute hash with attributes zeroed (IGNORE_ATTRIBUTES flag)
756+
let servtd_attr = SERVTD_ATTR_IGNORE_ATTRIBUTES;
757+
let mut zeroed = tdinfo_bytes;
758+
zeroed[0..8].fill(0); // zero attributes for hash computation
759+
let info_hash = digest_sha384(&zeroed).unwrap();
760+
let mut buffer = [0u8; SHA384_DIGEST_SIZE + size_of::<u16>() + size_of::<u64>()];
761+
buffer[..SHA384_DIGEST_SIZE].copy_from_slice(&info_hash);
762+
buffer[SHA384_DIGEST_SIZE..SHA384_DIGEST_SIZE + 2]
763+
.copy_from_slice(&SERVTD_TYPE_MIGTD.to_le_bytes());
764+
buffer[SHA384_DIGEST_SIZE + 2..SHA384_DIGEST_SIZE + 10]
765+
.copy_from_slice(&servtd_attr.to_le_bytes());
766+
let expected_hash = digest_sha384(&buffer).unwrap();
767+
768+
let result = verify_servtd_hash(&tdinfo_bytes, servtd_attr, &expected_hash);
769+
assert!(result.is_ok());
770+
}
771+
772+
#[test]
773+
fn test_verify_servtd_hash_with_ignore_mrowner() {
774+
// Build TdInfo with non-zero mrowner at offset 112..160
775+
let mut tdinfo_bytes = [0u8; 512];
776+
tdinfo_bytes[112..160].copy_from_slice(&[0xAA; 48]); // mrowner
777+
778+
// Compute hash with mrowner zeroed (IGNORE_MROWNER flag)
779+
let servtd_attr = SERVTD_ATTR_IGNORE_MROWNER;
780+
let mut zeroed = tdinfo_bytes;
781+
zeroed[112..160].fill(0);
782+
let info_hash = digest_sha384(&zeroed).unwrap();
783+
let mut buffer = [0u8; SHA384_DIGEST_SIZE + size_of::<u16>() + size_of::<u64>()];
784+
buffer[..SHA384_DIGEST_SIZE].copy_from_slice(&info_hash);
785+
buffer[SHA384_DIGEST_SIZE..SHA384_DIGEST_SIZE + 2]
786+
.copy_from_slice(&SERVTD_TYPE_MIGTD.to_le_bytes());
787+
buffer[SHA384_DIGEST_SIZE + 2..SHA384_DIGEST_SIZE + 10]
788+
.copy_from_slice(&servtd_attr.to_le_bytes());
789+
let expected_hash = digest_sha384(&buffer).unwrap();
790+
791+
let result = verify_servtd_hash(&tdinfo_bytes, servtd_attr, &expected_hash);
792+
assert!(result.is_ok());
793+
// mrowner should be zeroed in the returned TdInfo
794+
assert_eq!(result.unwrap().mrowner, [0u8; 48]);
795+
}
796+
797+
#[test]
798+
fn test_get_rtmrs_from_tdinfo() {
799+
use tdx_tdcall::tdreport::TdInfo;
800+
let mut tdinfo_bytes = [0u8; 512];
801+
// RTMR offsets in TdInfo: rtmr0 at 208, rtmr1 at 256, rtmr2 at 304, rtmr3 at 352
802+
tdinfo_bytes[208..256].copy_from_slice(&[0x01; 48]); // rtmr0
803+
tdinfo_bytes[256..304].copy_from_slice(&[0x02; 48]); // rtmr1
804+
tdinfo_bytes[304..352].copy_from_slice(&[0x03; 48]); // rtmr2
805+
tdinfo_bytes[352..400].copy_from_slice(&[0x04; 48]); // rtmr3
806+
807+
let td_info = unsafe {
808+
let mut uninit = core::mem::MaybeUninit::<TdInfo>::uninit();
809+
core::ptr::copy_nonoverlapping(
810+
tdinfo_bytes.as_ptr(),
811+
uninit.as_mut_ptr() as *mut u8,
812+
size_of::<TdInfo>(),
813+
);
814+
uninit.assume_init()
815+
};
816+
817+
let rtmrs = get_rtmrs_from_tdinfo(&td_info).unwrap();
818+
assert_eq!(rtmrs[0], [0x01; 48]);
819+
assert_eq!(rtmrs[1], [0x02; 48]);
820+
assert_eq!(rtmrs[2], [0x03; 48]);
821+
assert_eq!(rtmrs[3], [0x04; 48]);
822+
}
708823
}
709824

710825
fn get_rtmrs_from_suppl_data(

src/migtd/src/migration/rebinding.rs

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,9 @@ async fn rebinding_old_prepare(
614614
Ok(())
615615
}
616616

617-
#[cfg(not(feature = "spdm_attestation"))]
618617

618+
619+
#[cfg(not(feature = "spdm_attestation"))]
619620
async fn rebinding_new_prepare(
620621
transport: TransportType,
621622
info: &RebindingInfo,
@@ -654,6 +655,8 @@ async fn rebinding_new_prepare(
654655
Ok(())
655656
}
656657

658+
659+
657660
pub fn write_rebinding_session_token(rebind_token: &[u8]) -> Result<(), MigrationResult> {
658661
if rebind_token.len() != 32 {
659662
return Err(MigrationResult::InvalidParameter);
@@ -810,3 +813,185 @@ async fn tls_session_read_exact(
810813
}
811814
Ok(())
812815
}
816+
817+
#[cfg(test)]
818+
mod test {
819+
use super::*;
820+
use alloc::vec;
821+
822+
/// Build a minimal valid MIGTD_DATA blob containing one TDINFO_STRUCT entry.
823+
fn build_migtd_data(tdinfo: &[u8]) -> Vec<u8> {
824+
let mut buf = Vec::new();
825+
buf.extend_from_slice(MIGTD_DATA_SIGNATURE); // "MIGTDATA"
826+
buf.extend_from_slice(&0x00010000u32.to_le_bytes()); // version
827+
buf.extend_from_slice(&0u32.to_le_bytes()); // length placeholder
828+
buf.extend_from_slice(&1u32.to_le_bytes()); // num_entries = 1
829+
// Entry: type 0 (TDINFO)
830+
buf.extend_from_slice(&MIGTD_DATA_TYPE_TDINFO.to_le_bytes());
831+
buf.extend_from_slice(&(tdinfo.len() as u32).to_le_bytes());
832+
buf.extend_from_slice(tdinfo);
833+
// Patch length
834+
let total = buf.len() as u32;
835+
buf[12..16].copy_from_slice(&total.to_le_bytes());
836+
buf
837+
}
838+
839+
/// Create a 512-byte TDINFO_STRUCT with known mrowner and mrownerconfig.
840+
fn make_tdinfo(mrowner: &[u8; 48], mrownerconfig: &[u8; 48]) -> Vec<u8> {
841+
let mut tdinfo = vec![0u8; 512];
842+
// mrowner at offset 112..160
843+
tdinfo[112..160].copy_from_slice(mrowner);
844+
// mrownerconfig at offset 160..208
845+
tdinfo[160..208].copy_from_slice(mrownerconfig);
846+
tdinfo
847+
}
848+
849+
// --- InitData tests ---
850+
851+
#[test]
852+
fn test_initdata_read_write_roundtrip() {
853+
let mrowner = [0xAAu8; 48];
854+
let mrownerconfig = [0xBBu8; 48];
855+
let tdinfo = make_tdinfo(&mrowner, &mrownerconfig);
856+
857+
let data = build_migtd_data(&tdinfo);
858+
let init = InitData::read_from_bytes(&data).expect("should parse valid MIGTD_DATA");
859+
860+
assert_eq!(init.init_tdinfo.len(), 512);
861+
assert_eq!(init.init_tdinfo, tdinfo);
862+
863+
// Round-trip: write back and re-parse
864+
let mut buf = Vec::new();
865+
init.write_into_bytes(&mut buf);
866+
let init2 = InitData::read_from_bytes(&buf).expect("round-trip should parse");
867+
assert_eq!(init2.init_tdinfo, tdinfo);
868+
}
869+
870+
#[test]
871+
fn test_initdata_mrowner_mrownerconfig() {
872+
let mrowner = [0x11u8; 48];
873+
let mrownerconfig = [0x22u8; 48];
874+
let tdinfo = make_tdinfo(&mrowner, &mrownerconfig);
875+
let data = build_migtd_data(&tdinfo);
876+
877+
let init = InitData::read_from_bytes(&data).unwrap();
878+
assert_eq!(init.mrowner(), &mrowner);
879+
assert_eq!(init.mrownerconfig(), &mrownerconfig);
880+
}
881+
882+
#[test]
883+
fn test_initdata_rejects_bad_signature() {
884+
let tdinfo = vec![0u8; 512];
885+
let mut data = build_migtd_data(&tdinfo);
886+
data[0] = b'X'; // corrupt signature
887+
assert!(InitData::read_from_bytes(&data).is_none());
888+
}
889+
890+
#[test]
891+
fn test_initdata_rejects_bad_version() {
892+
let tdinfo = vec![0u8; 512];
893+
let mut data = build_migtd_data(&tdinfo);
894+
data[8..12].copy_from_slice(&0x00020000u32.to_le_bytes()); // wrong version
895+
assert!(InitData::read_from_bytes(&data).is_none());
896+
}
897+
898+
#[test]
899+
fn test_initdata_rejects_multiple_entries() {
900+
let tdinfo = vec![0u8; 512];
901+
let mut data = build_migtd_data(&tdinfo);
902+
data[16..20].copy_from_slice(&2u32.to_le_bytes()); // num_entries = 2
903+
assert!(InitData::read_from_bytes(&data).is_none());
904+
}
905+
906+
#[test]
907+
fn test_initdata_rejects_wrong_type() {
908+
let tdinfo = vec![0u8; 512];
909+
let mut data = build_migtd_data(&tdinfo);
910+
data[20..24].copy_from_slice(&1u32.to_le_bytes()); // type 1 instead of 0
911+
assert!(InitData::read_from_bytes(&data).is_none());
912+
}
913+
914+
#[test]
915+
fn test_initdata_rejects_short_tdinfo() {
916+
let tdinfo = vec![0u8; 256]; // too small (< 512)
917+
let data = build_migtd_data(&tdinfo);
918+
assert!(InitData::read_from_bytes(&data).is_none());
919+
}
920+
921+
#[test]
922+
fn test_initdata_rejects_empty() {
923+
assert!(InitData::read_from_bytes(&[]).is_none());
924+
assert!(InitData::read_from_bytes(&[0u8; 10]).is_none());
925+
}
926+
927+
// --- RebindingInfo tests ---
928+
929+
/// Build a minimal RebindingInfo byte buffer.
930+
fn build_rebinding_info(
931+
mig_request_id: u64,
932+
rebinding_src: u8,
933+
has_init_data: u8,
934+
uuid: [u64; 4],
935+
binding_handle: u64,
936+
init_data: Option<&[u8]>,
937+
) -> Vec<u8> {
938+
let mut buf = Vec::new();
939+
buf.extend_from_slice(&mig_request_id.to_le_bytes()); // 0..8
940+
buf.push(rebinding_src); // 8
941+
buf.push(has_init_data); // 9
942+
buf.extend_from_slice(&[0u8; 6]); // 10..16 reserved
943+
for u in &uuid {
944+
buf.extend_from_slice(&u.to_le_bytes()); // 16..48
945+
}
946+
buf.extend_from_slice(&binding_handle.to_le_bytes()); // 48..56
947+
if let Some(data) = init_data {
948+
buf.extend_from_slice(data);
949+
}
950+
buf
951+
}
952+
953+
#[test]
954+
fn test_rebinding_info_no_init_data() {
955+
let buf = build_rebinding_info(42, 1, 0, [1, 2, 3, 4], 99, None);
956+
let info = RebindingInfo::read_from_bytes(&buf).expect("should parse");
957+
assert_eq!(info.mig_request_id, 42);
958+
assert_eq!(info.rebinding_src, 1);
959+
assert_eq!(info.has_init_data, 0);
960+
assert_eq!(info.target_td_uuid, [1, 2, 3, 4]);
961+
assert_eq!(info.binding_handle, 99);
962+
assert!(info.init_migtd_data.is_none());
963+
}
964+
965+
#[test]
966+
fn test_rebinding_info_with_init_data() {
967+
let tdinfo = make_tdinfo(&[0xCAu8; 48], &[0xFEu8; 48]);
968+
let migtd_data = build_migtd_data(&tdinfo);
969+
let buf = build_rebinding_info(7, 0, 1, [10, 20, 30, 40], 55, Some(&migtd_data));
970+
let info = RebindingInfo::read_from_bytes(&buf).expect("should parse with init data");
971+
assert_eq!(info.mig_request_id, 7);
972+
assert_eq!(info.has_init_data, 1);
973+
let init = info.init_migtd_data.as_ref().unwrap();
974+
assert_eq!(init.mrowner(), &[0xCAu8; 48]);
975+
assert_eq!(init.mrownerconfig(), &[0xFEu8; 48]);
976+
}
977+
978+
#[test]
979+
fn test_rebinding_info_rejects_short_buffer() {
980+
assert!(RebindingInfo::read_from_bytes(&[0u8; 10]).is_none());
981+
assert!(RebindingInfo::read_from_bytes(&[0u8; 55]).is_none()); // 55 < 56
982+
}
983+
984+
#[test]
985+
fn test_rebinding_info_rejects_nonzero_reserved() {
986+
let mut buf = build_rebinding_info(1, 0, 0, [0; 4], 0, None);
987+
buf[10] = 0xFF; // reserved byte not zero
988+
assert!(RebindingInfo::read_from_bytes(&buf).is_none());
989+
}
990+
991+
#[test]
992+
fn test_rebinding_info_rejects_has_init_data_without_data() {
993+
// has_init_data=1 but no data bytes following → InitData::read_from_bytes fails
994+
let buf = build_rebinding_info(1, 0, 1, [0; 4], 0, None);
995+
assert!(RebindingInfo::read_from_bytes(&buf).is_none());
996+
}
997+
}

0 commit comments

Comments
 (0)