Skip to content

Commit 57d1e06

Browse files
ghci_20260214: add unit tests
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
1 parent 6035099 commit 57d1e06

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,
@@ -651,6 +652,8 @@ async fn rebinding_new_prepare(
651652
Ok(())
652653
}
653654

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

0 commit comments

Comments
 (0)