Skip to main content

igvmfilegen/
measurement_diag.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Per-platform launch measurement diagnostics.
5//!
6//! Builds the human-readable launch-measurement structures that downstream
7//! signing/attestation tooling expects (`VBS_VM_BOOT_MEASUREMENT_SIGNED_DATA`
8//! for VBS, `SnpPspIdBlock` for SEV-SNP, MRTD for TDX) and emits them via
9//! `tracing` so they are visible in `igvmfilegen` output. The `igvm` crate
10//! itself only computes the raw digest; the diagnostic dressing (svn,
11//! debug bit, SNP family/image identifiers, ...) is OpenHCL-specific and
12//! lives here.
13
14use bitfield_struct::bitfield;
15use igvm::IgvmFile;
16use igvm::IgvmInitializationHeader;
17use igvm_defs::IgvmPlatformType;
18use igvm_defs::VbsDigestAlgorithm;
19use igvm_defs::VbsSigningAlgorithm;
20use x86defs::snp::SnpPspIdBlock;
21use zerocopy::Immutable;
22use zerocopy::IntoBytes;
23use zerocopy::KnownLayout;
24
25use crate::snp_id_block::SNP_FAMILY_ID;
26use crate::snp_id_block::SNP_IMAGE_ID;
27
28// Name follows the Windows VBS C struct convention; `#[repr(C)]` already
29// silences the `non_camel_case_types` lint so no explicit allow is needed.
30#[repr(C)]
31#[derive(IntoBytes, Immutable, KnownLayout, Debug)]
32struct VBS_VM_BOOT_MEASUREMENT_SIGNED_DATA {
33    version: u32,
34    product_id: u32,
35    module_id: u32,
36    security_version: u32,
37    security_policy: VBS_POLICY_FLAGS,
38    boot_digest_algo: u32,
39    signing_algo: u32,
40    boot_measurement_digest: [u8; 32],
41}
42
43/// Flags defining the security policy for the guest.
44#[bitfield(u32)]
45#[derive(IntoBytes, Immutable, KnownLayout)]
46#[expect(non_camel_case_types)]
47struct VBS_POLICY_FLAGS {
48    /// Guest supports debugging
49    #[bits(1)]
50    debug: bool,
51    #[bits(31)]
52    reserved: u32,
53}
54
55/// Emit a `tracing` log of the platform-specific launch-measurement
56/// diagnostic structure for human inspection.
57pub fn log_measurement_diagnostic(
58    platform: IgvmPlatformType,
59    digest: &[u8],
60    svn: u32,
61    enable_debug: bool,
62    file: &IgvmFile,
63    compatibility_mask: u32,
64) {
65    match platform {
66        IgvmPlatformType::VSM_ISOLATION => log_vbs(digest, svn, enable_debug),
67        IgvmPlatformType::SEV_SNP => log_snp(digest, svn, file, compatibility_mask),
68        IgvmPlatformType::TDX => log_tdx(digest),
69        _ => {}
70    }
71}
72
73fn log_vbs(digest: &[u8], svn: u32, enable_debug: bool) {
74    const MSFT_PRODUCT_ID: u32 = u32::from_le_bytes(*b"msft");
75    const VBS_MODULE_ID: u32 = u32::from_le_bytes(*b"vbs\0");
76    const VBS_VM_BOOT_MEASUREMENT_VERSION_CURRENT: u32 = 0x1;
77
78    // The digest comes from `IgvmSerializer::measurement_for(VSM_ISOLATION)`
79    // which contractually returns a 32-byte SHA-256. A length mismatch
80    // would indicate a broken in-tree invariant.
81    let boot_measurement_digest =
82        <[u8; 32]>::try_from(digest).expect("VBS launch digest is 32 bytes");
83
84    let boot_measurement = VBS_VM_BOOT_MEASUREMENT_SIGNED_DATA {
85        version: VBS_VM_BOOT_MEASUREMENT_VERSION_CURRENT,
86        product_id: MSFT_PRODUCT_ID,
87        module_id: VBS_MODULE_ID,
88        security_version: svn,
89        security_policy: VBS_POLICY_FLAGS::new().with_debug(enable_debug),
90        boot_digest_algo: VbsDigestAlgorithm::SHA256.0,
91        signing_algo: VbsSigningAlgorithm::ECDSA_P384.0,
92        boot_measurement_digest,
93    };
94    tracing::info!("Boot Measurement {:x?}", boot_measurement);
95}
96
97fn log_snp(digest: &[u8], svn: u32, file: &IgvmFile, compatibility_mask: u32) {
98    // The digest comes from `IgvmSerializer::measurement_for(SEV_SNP)`
99    // which contractually returns a 48-byte SHA-384. A length mismatch
100    // would indicate a broken in-tree invariant.
101    let ld = <[u8; 48]>::try_from(digest).expect("SNP launch digest is 48 bytes");
102
103    let policy = file
104        .initializations()
105        .iter()
106        .find_map(|h| match h {
107            IgvmInitializationHeader::GuestPolicy {
108                policy,
109                compatibility_mask: mask,
110            } if mask & compatibility_mask == compatibility_mask => Some(*policy),
111            _ => None,
112        })
113        .unwrap_or_else(|| {
114            tracing::error!(
115                compatibility_mask = format_args!("0x{compatibility_mask:X}"),
116                "Missing SNP GuestPolicy initialization header; reporting policy as 0"
117            );
118            0
119        });
120
121    let psp_id_block = SnpPspIdBlock {
122        ld,
123        family_id: SNP_FAMILY_ID,
124        image_id: SNP_IMAGE_ID,
125        version: 0x1,
126        guest_svn: svn,
127        policy,
128    };
129    tracing::info!("SNP ID Block {:x?}", psp_id_block);
130}
131
132fn log_tdx(digest: &[u8]) {
133    tracing::info!("MRTD: {}", hex::encode_upper(digest));
134}