@@ -10,7 +10,8 @@ use anyhow::{anyhow, bail, Context, Result};
1010use cc_eventlog:: { RuntimeEvent , TdxEvent } ;
1111use dcap_qvl:: {
1212 quote:: { EnclaveReport , Quote , Report , TDReport10 , TDReport15 } ,
13- verify:: VerifiedReport as TdxVerifiedReport ,
13+ verify:: { QuoteVerificationResult , VerifiedReport as TdxVerifiedReport } ,
14+ Policy , SimplePolicy , TcbStatus ,
1415} ;
1516#[ cfg( feature = "quote" ) ]
1617use dstack_types:: SysConfig ;
@@ -195,20 +196,46 @@ impl QuoteContentType<'_> {
195196}
196197
197198#[ allow( clippy:: large_enum_variant) ]
198- /// Represents a verified attestation
199+ /// Represents a verified attestation report.
200+ ///
201+ /// For TDX, holds the full [`QuoteVerificationResult`] so that callers
202+ /// can apply additional (stricter) policies via [`QuoteVerificationResult::validate`]
203+ /// before converting to a [`VerifiedReport`](TdxVerifiedReport).
199204#[ derive( Clone ) ]
200205pub enum DstackVerifiedReport {
201- DstackTdx ( TdxVerifiedReport ) ,
206+ DstackTdx ( QuoteVerificationResult ) ,
202207 DstackGcpTdx ,
203208 DstackNitroEnclave ,
204209}
205210
206211impl DstackVerifiedReport {
207- pub fn tdx_report ( & self ) -> Option < & TdxVerifiedReport > {
212+ /// Get the TDX [`QuoteVerificationResult`] for further policy validation.
213+ pub fn tdx_qvr ( & self ) -> Option < & QuoteVerificationResult > {
208214 match self {
209- DstackVerifiedReport :: DstackTdx ( report) => Some ( report) ,
210- DstackVerifiedReport :: DstackGcpTdx => None ,
211- DstackVerifiedReport :: DstackNitroEnclave => None ,
215+ DstackVerifiedReport :: DstackTdx ( qvr) => Some ( qvr) ,
216+ _ => None ,
217+ }
218+ }
219+
220+ /// Consume self and apply a policy to the TDX quote, returning a [`TdxVerifiedReport`].
221+ ///
222+ /// Returns `None` for non-TDX reports.
223+ pub fn validate_tdx ( self , policy : & dyn Policy ) -> Result < Option < TdxVerifiedReport > > {
224+ match self {
225+ DstackVerifiedReport :: DstackTdx ( qvr) => {
226+ Ok ( Some ( qvr. validate ( policy) ?) )
227+ }
228+ _ => Ok ( None ) ,
229+ }
230+ }
231+
232+ /// Get a [`TdxVerifiedReport`] without additional policy checks.
233+ ///
234+ /// Safe to call because the baseline policy was already validated during verification.
235+ pub fn tdx_report ( & self ) -> Option < TdxVerifiedReport > {
236+ match self {
237+ DstackVerifiedReport :: DstackTdx ( qvr) => Some ( qvr. clone ( ) . into_report_unchecked ( ) ) ,
238+ _ => None ,
212239 }
213240 }
214241}
@@ -375,7 +402,9 @@ impl GetDeviceId for () {
375402impl GetDeviceId for DstackVerifiedReport {
376403 fn get_devide_id ( & self ) -> Vec < u8 > {
377404 match self {
378- DstackVerifiedReport :: DstackTdx ( tdx_report) => tdx_report. ppid . to_vec ( ) ,
405+ DstackVerifiedReport :: DstackTdx ( qvr) => {
406+ qvr. clone ( ) . into_report_unchecked ( ) . ppid
407+ }
379408 DstackVerifiedReport :: DstackGcpTdx => Vec :: new ( ) ,
380409 DstackVerifiedReport :: DstackNitroEnclave => Vec :: new ( ) ,
381410 }
@@ -638,12 +667,12 @@ impl Attestation {
638667 pub async fn verify_with_time (
639668 self ,
640669 pccs_url : Option < & str > ,
641- _now : Option < SystemTime > ,
670+ now : Option < SystemTime > ,
642671 ) -> Result < VerifiedAttestation > {
643672 let report = match & self . quote {
644673 AttestationQuote :: DstackTdx ( q) => {
645- let report = self . verify_tdx ( pccs_url, & q. quote ) . await ?;
646- DstackVerifiedReport :: DstackTdx ( report )
674+ let qvr = self . verify_tdx ( pccs_url, & q. quote , now ) . await ?;
675+ DstackVerifiedReport :: DstackTdx ( qvr )
647676 }
648677 AttestationQuote :: DstackGcpTdx | AttestationQuote :: DstackNitroEnclave => {
649678 bail ! ( "Unsupported attestation mode: {:?}" , self . quote. mode( ) ) ;
@@ -682,7 +711,12 @@ impl Attestation {
682711 self . verify_with_time ( pccs_url, None ) . await
683712 }
684713
685- async fn verify_tdx ( & self , pccs_url : Option < & str > , quote : & [ u8 ] ) -> Result < TdxVerifiedReport > {
714+ async fn verify_tdx (
715+ & self ,
716+ pccs_url : Option < & str > ,
717+ quote : & [ u8 ] ,
718+ now : Option < SystemTime > ,
719+ ) -> Result < QuoteVerificationResult > {
686720 let mut pccs_url = Cow :: Borrowed ( pccs_url. unwrap_or_default ( ) ) ;
687721 if pccs_url. is_empty ( ) {
688722 // try to read from PCCS_URL env var
@@ -691,13 +725,26 @@ impl Attestation {
691725 Err ( _) => Cow :: Borrowed ( "" ) ,
692726 } ;
693727 }
694- let tdx_report =
728+ let now_secs = now
729+ . unwrap_or_else ( SystemTime :: now)
730+ . duration_since ( SystemTime :: UNIX_EPOCH )
731+ . context ( "system time before epoch" ) ?
732+ . as_secs ( ) ;
733+ let qvr =
695734 dcap_qvl:: collateral:: get_collateral_and_verify ( quote, Some ( pccs_url. as_ref ( ) ) )
696735 . await
697736 . context ( "Failed to get collateral" ) ?;
698- validate_tcb ( & tdx_report) ?;
699737
700- let td_report = tdx_report. report . as_td10 ( ) . context ( "no td report" ) ?;
738+ // Baseline policy validation (business layer can apply stricter policies on the returned QVR)
739+ let supplemental = qvr. supplemental ( ) . context ( "Failed to build supplemental data" ) ?;
740+ default_policy ( now_secs)
741+ . validate ( & supplemental)
742+ . context ( "TCB policy validation failed" ) ?;
743+
744+ // Validate TEE attributes (debug mode, signer, etc.)
745+ validate_tcb ( & supplemental. report ) ?;
746+
747+ let td_report = supplemental. report . as_td10 ( ) . context ( "no td report" ) ?;
701748 let replayed_rtmr = self . replay_runtime_events :: < Sha384 > ( None ) ;
702749 if replayed_rtmr != td_report. rt_mr3 {
703750 bail ! (
@@ -710,12 +757,12 @@ impl Attestation {
710757 if td_report. report_data != self . report_data [ ..] {
711758 bail ! ( "tdx report_data mismatch" ) ;
712759 }
713- Ok ( tdx_report )
760+ Ok ( qvr )
714761 }
715762}
716763
717- /// Validate the TCB attributes
718- pub fn validate_tcb ( report : & TdxVerifiedReport ) -> Result < ( ) > {
764+ /// Validate the TEE report attributes (debug mode, signer, etc.)
765+ pub fn validate_tcb ( report : & Report ) -> Result < ( ) > {
719766 fn validate_td10 ( report : & TDReport10 ) -> Result < ( ) > {
720767 let is_debug = report. td_attributes [ 0 ] & 0x01 != 0 ;
721768 if is_debug {
@@ -739,13 +786,37 @@ pub fn validate_tcb(report: &TdxVerifiedReport) -> Result<()> {
739786 }
740787 Ok ( ( ) )
741788 }
742- match & report . report {
789+ match report {
743790 Report :: TD15 ( report) => validate_td15 ( report) ,
744791 Report :: TD10 ( report) => validate_td10 ( report) ,
745792 Report :: SgxEnclave ( report) => validate_sgx ( report) ,
746793 }
747794}
748795
796+ /// Default TCB policy for dstack attestation.
797+ ///
798+ /// Accepts common non-critical TCB statuses (`SWHardeningNeeded`, `ConfigurationNeeded`,
799+ /// `ConfigurationAndSWHardeningNeeded`, `OutOfDate`, `OutOfDateConfigurationNeeded`)
800+ /// with a 90-day collateral grace period, and allows SMT (hyperthreading).
801+ ///
802+ /// Rejects quotes carrying high-severity advisories that directly compromise TEE guarantees:
803+ /// - INTEL-SA-01397: TDX migration → debuggable TD (CVSS 8.4, full TDX compromise)
804+ /// - INTEL-SA-01367: OOB write in SGX/TDX memory subsystem (CVSS 7.2)
805+ /// - INTEL-SA-01314: OOB write in TDX module
806+ /// - INTEL-SA-00837: Unauthorized error injection in SGX/TDX (CVSS 7.2)
807+ pub fn default_policy ( now_secs : u64 ) -> SimplePolicy {
808+ use core:: time:: Duration ;
809+ SimplePolicy :: strict ( now_secs)
810+ . allow_status ( TcbStatus :: OutOfDate )
811+ . platform_grace_period ( Duration :: from_secs ( 30 * 24 * 3600 ) )
812+ . qe_grace_period ( Duration :: from_secs ( 14 * 24 * 3600 ) )
813+ . allow_smt ( true )
814+ . reject_advisory ( "INTEL-SA-01397" )
815+ . reject_advisory ( "INTEL-SA-01367" )
816+ . reject_advisory ( "INTEL-SA-01314" )
817+ . reject_advisory ( "INTEL-SA-00837" )
818+ }
819+
749820/// Information about the app extracted from event log
750821#[ derive( Debug , Clone , Serialize , Deserialize ) ]
751822pub struct AppInfo {
0 commit comments