From 948dbd02b690a45f9447255a6599421fef6eba7b Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 8 Aug 2026 06:42:11 -0700 Subject: [PATCH 1/2] fix(attestation): serialize TDX V2 event preimages --- dstack/dstack-attest/src/attestation.rs | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index 7486fa0ea..1c0049895 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -2414,7 +2414,11 @@ impl Attestation { /// msgpack wire format so the `version` field is preserved (SCALE /// V0 skips it for legacy binary compat). Otherwise default to V0 for /// backward compat with callers that expect the SCALE format. - pub fn into_versioned(self) -> VersionedAttestation { + pub fn into_versioned(mut self) -> VersionedAttestation { + // V2 event digests cannot be reconstructed from the serialized event + // fields alone. Populate their canonical preimages before the legacy + // quote is projected into either wire schema. + self.fill_event_preimages(); let has_v2 = self .runtime_events .iter() @@ -2990,6 +2994,17 @@ mod tests { #[test] fn into_versioned_upgrades_to_v1_when_any_event_is_v2() { let mut att = dummy_tdx_attestation([8u8; 64]); + let AttestationQuote::DstackTdx(tdx_quote) = &mut att.quote else { + panic!("expected TDX attestation"); + }; + tdx_quote.event_log.push( + cc_eventlog::RuntimeEvent::new( + "compose-hash".into(), + vec![4, 5, 6], + cc_eventlog::EventLogVersion::V2, + ) + .into(), + ); att.runtime_events.push(cc_eventlog::RuntimeEvent::new( "app-id".into(), vec![1, 2, 3], @@ -3000,11 +3015,20 @@ mod tests { vec![4, 5, 6], cc_eventlog::EventLogVersion::V2, )); - let versioned = att.into_versioned(); + let encoded = att.into_versioned().to_bytes().unwrap(); + let VersionedAttestation::V1 { attestation } = + VersionedAttestation::from_bytes(&encoded).unwrap() + else { + panic!("presence of a V2 event must force the V1 msgpack wire format"); + }; + let PlatformEvidence::Tdx { event_log, .. } = attestation.platform else { + panic!("expected TDX platform evidence"); + }; assert!( - matches!(versioned, VersionedAttestation::V1 { .. }), - "presence of a V2 event must force the V1 msgpack wire format to preserve `version`" + event_log[0].preimage.is_some(), + "serialized V2 TDX events must carry their canonical digest preimage" ); + cc_eventlog::tdx::validate_v2_preimages(&event_log).unwrap(); } fn v1_event(event: String, payload: Vec) -> RuntimeEvent { RuntimeEvent::new(event, payload, EventLogVersion::V1) From 7f3952bb2075b305ac0cdbab5028d8ec913776f1 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 8 Aug 2026 12:20:36 -0700 Subject: [PATCH 2/2] fix(attestation): preserve stripped V2 event digests --- dstack/cc-eventlog/src/tdx.rs | 13 ++++++++++++- dstack/dstack-attest/src/attestation.rs | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/dstack/cc-eventlog/src/tdx.rs b/dstack/cc-eventlog/src/tdx.rs index 98682ea7e..d4bd94e49 100644 --- a/dstack/cc-eventlog/src/tdx.rs +++ b/dstack/cc-eventlog/src/tdx.rs @@ -85,7 +85,7 @@ impl TdxEvent { Self { imr: self.imr, event_type: self.event_type, - digest: Vec::new(), + digest: self.digest.clone(), event: self.event.clone(), event_payload: self.event_payload.clone(), version: self.version, @@ -322,6 +322,17 @@ mod tests { assert_eq!(actual.as_slice(), &tdx.digest); } + #[test] + fn stripped_v2_runtime_event_preserves_digest_binding() { + let mut event = v2_event(); + event.fill_preimage(); + + let stripped = event.stripped(); + + assert_eq!(stripped.digest, event.digest); + validate_v2_preimages(&[stripped]).expect("stripped V2 event remains verifiable"); + } + #[test] fn fill_preimage_skips_non_runtime_events() { let mut boot_event = TdxEvent::new(0, 0x1, "EV_POST_CODE".to_string(), vec![1, 2, 3]); diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index 1c0049895..e55d63827 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -3015,7 +3015,9 @@ mod tests { vec![4, 5, 6], cc_eventlog::EventLogVersion::V2, )); - let encoded = att.into_versioned().to_bytes().unwrap(); + // RA-TLS certificates use the stripped representation. Its runtime + // events must retain the advertised digest paired with each preimage. + let encoded = att.into_versioned().into_stripped().to_bytes().unwrap(); let VersionedAttestation::V1 { attestation } = VersionedAttestation::from_bytes(&encoded).unwrap() else {