validate_v2_preimages is documented as binding RTMR replay to displayed fields, but inspects the event list that is not replayed
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
Hướng nghiên cứu
Start with validate_v2_preimages in cc-eventlog/src/tdx.rs and the event handling in dstack-attest/src/attestation.rs, v1.rs, and VersionedAttestation::from_bytes. Compare platform event_log with runtime_events, review Attestation::from_tdx_quote and into_stripped, and run or add a divergent-pair test; done means the replayed and validated event representations cannot disagree and the misleading doc comment is corrected.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Label: DESIGN. The API shape is wrong, not a live decision. The demonstration below is of the shape, not of an exploit.
Base: origin/next @ 030fbb2183.
What the chain claims
cc-eventlog/src/tdx.rs:170-175, the doc comment on validate_v2_preimages:
Validate the externally supplied digest preimage of every V2 runtime event.
The preimage must be present, valid hex, hash to the advertised digest, and equal the canonical representation reconstructed from the public event fields. This binds RTMR replay and displayed fields to the same bytes.
What it enforces
validate_v2_preimages is called on q.event_log / q.tdx_quote.event_log — dstack-attest/src/attestation.rs:2389-2399.
The RTMR3 replay uses a different field: self.runtime_events — dstack-attest/src/attestation.rs:2521 (verify_tdx) and :1807 (verify_tdx_quote_with_events).
These are two independent fields on the wire:
- SCALE/V0:
Attestation { quote: AttestationQuote::DstackTdx(TdxQuote { event_log }), runtime_events, .. }—dstack-attest/src/attestation.rs:1449-1465. - msgpack/V1:
PlatformEvidence::Tdx { quote, event_log }(dstack-attest/src/v1.rs:66-70) versusStackEvidence::Dstack { report_data, runtime_events, config }(dstack-attest/src/v1.rs:199-206).
Nothing checks that they agree. from_msgpack (v1.rs:~305) and VersionedAttestation::from_bytes (attestation.rs:742-770) do not; neither does verify_with_time.
So the function documented as binding replay to displayed fields is applied to the list that is not replayed, and the list that is replayed carries no supplied digest for the function to check.
Demonstrated
Scratch crate outside the repo, path-depending on ra-tls and cc-eventlog, driving the checked-in sdk/simulator/attestation.bin:
platform.event_log entries = 33
stack.runtime_events entries = 9
app_id unmodified = 5bb4ff9a3837357f19dc176407a5709c62eb6c56
app_id with platform.event_log emptied = 5bb4ff9a3837357f19dc176407a5709c62eb6c56
app_id with stack.runtime_events emptied = "" (empty = true)
Emptying the list validate_v2_preimages inspects changes nothing about the decoded identity; emptying the other one empties it.
Reachability
- Who can trigger it: anyone who can submit an attestation blob —
POST /verifywithattestation(verifier/src/main.rs:95, no auth fairing), aSignCertRequestCSR, or an RA-TLS certificate extension. - What credential it needs: none for
POST /verify. - Who controls frequency: the submitter.
What it costs, concretely
The event log a consumer displays or exports and the event log a verifier replays can disagree, and the stated safety property is attached to the wrong one.
Today the blast radius is small and I want to be accurate about that: VerificationResponse does not echo the log (verifier/src/types.rs:80-100), and ct_monitor derives both lists from one blob via Attestation::from_tdx_quote (ct_monitor/src/main.rs:173, attestation.rs:2137-2161), so its two copies are consistent by construction. The exposure is to any consumer that reads platform.event_log out of an attestation blob it also verified — which the type invites, because both fields are public.
The one place platform.event_log does feed a check is TDX-lite ACPI (verifier/src/verification.rs:1059-1072), and that is safe: the reported digests are compared against recomputed ones and then discarded, and RTMR0 is rebuilt from the recomputed values (verification.rs:1074-1081).
Steelman
validate_v2_preimages is doing real work where it is: it is what lets a relying party reading the serialized TdxEvent list check a V2 event's digest field, and it rejects a non-canonical preimage that happens to hash to the advertised digest — pinned by rejects_noncanonical_v2_preimage_with_matching_digest (cc-eventlog/src/tdx.rs:397-404). Carrying two representations is a compatibility artefact rather than a design choice: RuntimeEvent is the semantic type and TdxEvent is the TCG-shaped one, and both were already on the wire when the V1 msgpack schema was written, so the schema recorded both.
It is also worth saying that the replay itself is sound: TdxEvent::digest() recomputes runtime-event digests from (name, payload, version) rather than trusting the supplied field (cc-eventlog/src/tdx.rs:121-126), and replay_events hashes those. There is no forgeable digest in the replay path — which is exactly why validate_v2_preimages has nothing to do there.
Improvement directions
- Cheap, closes it, wire-compatible. At decode time, derive
runtime_eventsfromplatform.event_log'simr == 3entries instead of accepting it as an independent field — which is precisely whatAttestation::from_tdx_quotealready does (attestation.rs:2137-2161). One field leaves the trusted surface; existing encoders keep working because they emit both consistently. Cost: one read-side change plus a test that a divergent pair no longer decodes two ways. - Cheaper, weaker. Add an equality check in
verify_with_timeand reject an attestation whose two lists disagree on theirimr == 3entries. Keeps the wire format exactly; costs one comparison per verification. Risk: any historical encoder that legitimately stripped one list differently would start failing —into_stripped(attestation.rs:804-830,v1.rs:20-62) stripsevent_logbut notruntime_events, so this option needs that asymmetry resolved first, which is why (1) is cleaner. - Minimum. Correct the doc comment at
cc-eventlog/src/tdx.rs:170-175to say what the function actually validates, so the next reader does not inherit the claim.
(1) is the one worth doing; (3) should happen regardless.
- Ngôn ngữ chính
- Rust
- Star
- 551
- Fork
- 97
- Merge trung bình
- 1 ngày 8 giờ
- Pull request đã merge (30 ngày)
- 182
Chuẩn bị môi trường
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Dstack-TEE/dstack
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Dstack-TEE/dstack#1384 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 30/100
Dstack-TEE/dstack#1301 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
Dstack-TEE/dstack#1300 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Dstack-TEE/dstack#1299 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Dstack-TEE/dstack#1298 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Dstack-TEE/dstack
Issue tương tự
-
area:casework bug criticality:p3 triage:needs-implementation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
registrystack/registry-stack#1623 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
leptos-rs/leptos#4885 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
longbridge/gpui-kit#3276 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
A-Migration Guides D-Straightforward S-Ready-For-Implementation X-Uncontroversial
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
bevyengine/bevy-website#2607 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày