validate_v2_preimages is documented as binding RTMR replay to displayed fields, but inspects the event list that is not replayed
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 5/5
- Tempo estimado
- Mais de uma semana
- Facilidade para iniciantes
- 35/100
Direção de pesquisa
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.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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.
- Linguagem predominante
- Rust
- Estrelas
- 551
- Forks
- 97
- Merge médio
- 1d 3h
- PRs com merge (30d)
- 199
Preparar o ambiente
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de Dstack-TEE/dstack
-
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 35/100
Dstack-TEE/dstack#1384 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 30/100
Dstack-TEE/dstack#1301 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 55/100
Dstack-TEE/dstack#1300 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
Dstack-TEE/dstack#1299 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
Dstack-TEE/dstack#1298 ·
Mantenedores costumam responder em até 1 dia
Todas as issues de Dstack-TEE/dstack
Issues semelhantes
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
vercel-labs/agent-browser#2017 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
tursodatabase/turso#9405 ·
Mantenedores costumam responder em até 1 dia
-
bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
PolyMeilex/Neothesia#447 ·
Mantenedores costumam responder em até 1 dia
-
backend::vllm diffusion multimodal
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
trezor/trezor-firmware#7985 ·
Mantenedores costumam responder em até 2 dias