From 58e211b188b2788e053b8cc5f7e3c367e831b2b3 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 7 Jan 2026 11:46:37 +0100 Subject: [PATCH] Validate XFAM values using a bitmask --- src/attestation/azure/mod.rs | 13 ++++++-- src/attestation/dcap.rs | 59 +++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/attestation/azure/mod.rs b/src/attestation/azure/mod.rs index 173f95b..395fc44 100644 --- a/src/attestation/azure/mod.rs +++ b/src/attestation/azure/mod.rs @@ -11,7 +11,9 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use x509_parser::prelude::*; -use crate::attestation::{dcap::verify_dcap_attestation, measurements::MultiMeasurements}; +use crate::attestation::{ + dcap::verify_dcap_attestation_with_given_timestamp, measurements::MultiMeasurements, +}; /// The attestation evidence payload that gets sent over the channel #[derive(Debug, Serialize, Deserialize)] @@ -108,8 +110,13 @@ async fn verify_azure_attestation_with_given_timestamp( // Do DCAP verification let tdx_quote_bytes = BASE64_URL_SAFE.decode(attestation_document.tdx_quote_base64)?; - let _dcap_measurements = - verify_dcap_attestation(tdx_quote_bytes, expected_tdx_input_data, pccs_url).await?; + let _dcap_measurements = verify_dcap_attestation_with_given_timestamp( + tdx_quote_bytes, + expected_tdx_input_data, + pccs_url, + now, + ) + .await?; let hcl_ak_pub = hcl_report.ak_pub()?; diff --git a/src/attestation/dcap.rs b/src/attestation/dcap.rs index 5094972..048bf34 100644 --- a/src/attestation/dcap.rs +++ b/src/attestation/dcap.rs @@ -11,6 +11,10 @@ use thiserror::Error; /// For fetching collateral directly from Intel, if no PCCS is specified pub const PCS_URL: &str = "https://api.trustedservices.intel.com"; +/// Allowed mask when validating XFAM values +/// FP, SSE, AVX, AVX512, plus reserved bit observed on GCP +const ALLOWED_XFAM_MASK: [u8; 8] = [0xe7, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00]; + /// Quote generation using configfs_tsm pub async fn create_dcap_attestation(input_data: [u8; 64]) -> Result, AttestationError> { let quote = generate_quote(input_data)?; @@ -32,7 +36,7 @@ pub async fn verify_dcap_attestation( } /// Allows the timestamp to be given, making it possible to test with existing attestations -async fn verify_dcap_attestation_with_given_timestamp( +pub async fn verify_dcap_attestation_with_given_timestamp( input: Vec, expected_input_data: [u8; 64], pccs_url: Option, @@ -51,6 +55,14 @@ async fn verify_dcap_attestation_with_given_timestamp( ) .await?; + validate_xfam( + quote + .report + .as_td10() + .ok_or(DcapVerificationError::SgxNotSupported)? + .xfam, + )?; + let _verified_report = dcap_qvl::verify::verify(&input, &collateral, now)?; let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; @@ -105,6 +117,23 @@ pub fn get_quote_input_data(report: Report) -> [u8; 64] { } } +/// Validate an XFAM value against policy mask +fn validate_xfam(xfam: [u8; 8]) -> Result<(), DcapVerificationError> { + tracing::debug!("Validating XFAM value: {xfam:?}"); + for (i, (&xfam_byte, &allowed_byte)) in xfam.iter().zip(ALLOWED_XFAM_MASK.iter()).enumerate() { + let disallowed_bits = allowed_byte & !xfam_byte; + + if disallowed_bits != 0 { + return Err(DcapVerificationError::InvalidXfam { + byte_index: i, + disallowed_bits, + }); + } + } + + Ok(()) +} + /// An error when verifying a DCAP attestation #[derive(Error, Debug)] pub enum DcapVerificationError { @@ -119,6 +148,11 @@ pub enum DcapVerificationError { #[cfg(test)] #[error("Quote parse: {0}")] QuoteParse(#[from] tdx_quote::QuoteParseError), + #[error("Invalid XFAM at index: {byte_index} value: {disallowed_bits}")] + InvalidXfam { + byte_index: usize, + disallowed_bits: u8, + }, } #[cfg(test)] @@ -169,4 +203,27 @@ mod tests { measurement_policy.check_measurement(&measurements).unwrap(); } + + #[test] + fn test_validate_xfam() { + // The mask itself + assert!(validate_xfam(ALLOWED_XFAM_MASK).is_ok()); + + // The value from a quote from Azure + assert!(validate_xfam([231, 24, 6, 0, 0, 0, 0, 0]).is_ok()); + + // The value from a quote from GCP + assert!(validate_xfam([231, 0, 6, 0, 0, 0, 0, 0]).is_ok()); + + // Remove one allowed bit from byte 0 (0xe7 -> 0xe6 clears bit0) + let xfam = [0xe6, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00]; + + assert!(matches!( + validate_xfam(xfam).unwrap_err(), + DcapVerificationError::InvalidXfam { + byte_index: 0, + disallowed_bits: 0x01 + } + )); + } }