GCP TDX image identity rests on one lane: MRTD/RTMR0-2 are never compared, and PCR7/PCR12 are not quoted although AWS NitroTPM binds both

Aperta
#1,296 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Funzionalità
Chiarezza
Da chiarire
Stato di attività
Attiva
Stack tecnologico
gcp, rust
Ambito
cloud, security

Direzione di ricerca

Start with dstack/verifier/src/verification.rs:1219-1278 and dstack/tpm-attest/src/lib.rs:33-35, then compare the GCP path with the AWS and TDX paths cited in the issue. Use .agent/TRUST-CHAIN-platform-parity.md, verifier/README.md, and docs/security/security-model.md to establish the current contract; done requires an explicit, scoped decision about the proposed GCP measurements and documentation.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

Classification: DESIGN. All claims argued from code — no GCP hardware was available, and nothing
here is demonstrated.

On GCP TDX the OS-image identity rests on exactly two comparisons: PCR0 against a hardcoded constant,
and one 32-byte UKI Authenticode digest. The same quote carries MRTD, RTMR0, RTMR1 and RTMR2, and
none of them is compared against anything — which is mostly a platform constraint, and I want to say
so up front. What is a dstack choice, and is the part worth deciding on, is that the quoted PCR
selection omits PCR7 (Secure Boot policy) and PCR12 (kernel command line / credentials), both of
which AWS NitroTPM — the other TPM platform in this tree — binds.

Tree: origin/next @ 030fbb2183.


1. What the design currently is

verify_os_image_hash_for_gcp_tdx, dstack/verifier/src/verification.rs:1219-1278:

const EXPECTED_PCR0: [u8; 32] =
    hex!("0cca9ec161b09288802e5a112255d21340ed5b797f5fe29cecccfd8f67b9f802");   // :1225-1226
...
if pcr0.value != EXPECTED_PCR0 { bail!("PCR 0 mismatch: expected GCP OVMF v2, ...") }   // :1257-1262
let event_28_digest = &pcr2_events.get(2).context("Event 28 not found")?.digest;       // :1263
if event_28_digest != expected_uki_hash { bail!("UKI hash mismatch...") }               // :1266-1272

expected_uki_hash is GcpOsImageMeasurement::uki_authenticode_sha256
(dstack/dstack-types/src/lib.rs:1618-1621) — the document's only field.

What is not compared.

  1. MRTD, RTMR0, RTMR1, RTMR2. All four are in the GCP quote. The GCP arm of the OS-image dispatch
    (verification.rs:812-814) computes no expected value for any of them, and decode_mr_gcp_tpm
    (dstack/dstack-attest/src/attestation.rs:1855-1879) builds mr_aggregated from PCR0, PCR2 and
    the replayed runtime PCR only — the TDX registers are not in the MR either. Compare TDX-legacy
    (verification.rs:1390-1417) and TDX-lite (:1089-1091), which compare all four.
  2. PCR7 and PCR12. The quoted selection is a constant in dstack's own code:
    dstack_pcr_policy() = sha256 [0, 2, 14], dstack/tpm-attest/src/lib.rs:33-35. AWS NitroTPM
    binds PCR4, PCR7 and PCR12 (AWS_NITRO_TPM_BOOT_PCRS, dstack-attest/src/attestation.rs:1273,
    compared at verification.rs:1209-1215).
  3. dstack.rootfs_hash. SEV-SNP (dstack-mr/src/sev.rs:118,760,876) and TDX
    (dstack-mr/src/tdx.rs:331) both require the measured cmdline to carry a well-formed
    dstack.rootfs_hash. There is no such call on the GCP path.

PCR0 as a firmware pin. EXPECTED_PCR0 is a single constant in source — not config, not an
allowlist, not versioned.

What GCP does well, for balance. Its runtime-event lane is the best in the tree: the guest
extends both RTMR3 and TPM PCR14 (dstack-attest/src/lib.rs:189-201), and the verifier replays and
compares both (attestation.rs:1808-1815 and :1090-1101), plus binds the vTPM quote to the TDX
quote via qualified_data == sha256(tdx_quote) (:1086-1089). Nothing in this issue is about the
runtime lane.

2. The steelman

(1) is close to forced. On GCP the TD is launched by Google's firmware and the payload is booted
through UEFI, so MRTD and RTMR0–2 describe a boot chain dstack neither builds nor publishes an
expected value for. There is no measurement.gcp.cbor field that could carry an expected MRTD,
because dstack does not produce GCP's TDVF. Routing image identity through the vTPM instead is a
reasonable response to that constraint, and the UKI Authenticode digest is a genuinely good anchor:
a UKI bundles kernel, cmdline and initrd into one PE image, so 32 bytes really do cover all three.

(3) follows from (1): with the cmdline inside the UKI, a separate dstack.rootfs_hash check adds
little.

(2) is the weakest link in the steelman. PCR7 and PCR12 are ordinary TPM PCRs available on the
same vTPM that already supplies PCR0, PCR2 and PCR14. The likely history is "we bound what the UKI
digest and the runtime lane needed", not a decision that Secure Boot state and external command-line
material are out of scope — especially since the sibling platform binds them.

(4) EXPECTED_PCR0 as a constant is a pragmatic pin on the GCP OVMF build that was current when
the path was written, and a constant fails closed, which is the right direction.

3. What it costs

One lane instead of two. TDX-legacy has two independent image checks — recompute every MR from
the downloaded image and replay the event log. GCP has one. If the vTPM lane is wrong for any
reason, the TDX registers sitting in the same quote will not catch it. This is not hypothetical as a
class: PR #1238 found that the UKI digest was being read from an event log that need never have been
quoted, and documents that POST /verify and the KMS path failed closed only "for an unrelated
reason … an accident in a different function written for a different purpose". #1238 fixes which
event log is read; it does not add a second lane.

Secure Boot state is not in the image identity on GCP but is on AWS. Whatever requireTcbUpToDate
and allowedOsImages mean on AWS, they mean strictly less on GCP in this respect.

Positional extraction. pcr2_events.get(2) (:1263) identifies "Event 28" by index rather than
by event type or description, with a GCP-OVMF-specific comment at :1254-1255. Any firmware change
that adds or reorders a PCR-2 event silently moves what is compared.

Agility. A GCP OVMF roll changes PCR0, EXPECTED_PCR0 stops matching, and every GCP verification
fails at once. The only remedy is a dstack release. That is an availability cost, not a security one,
but it is the kind of thing that gets worked around under pressure.

4. Improvement direction

Cheapest first.

  1. Free / cheap — write down what GCP binds and what it does not, in verifier/README.md and
    docs/security/security-model.md, including that MRTD/RTMR0–2 are present in the quote and not
    compared, and why. Today a reader of the GCP path reasonably assumes it behaves like the TDX path
    because it is the same quote format. (This overlaps the result-contract issue filed alongside
    this one.)
  2. Cheap, no measurement change — make EXPECTED_PCR0 a configured allowlist rather than a
    source constant, so a GCP firmware roll is an operator action rather than a release, and so more
    than one GCP OVMF build can be accepted during a transition. Keeps the fail-closed default.
  3. Cheap, composes with #1238 — match the PCR-2 event by type/description instead of
    pcr2_events.get(2). #1238 already changes this call site to read the attested subset; picking
    the event by identity rather than index is a natural addition rather than a conflict.
  4. Deployment event — add PCR7 and PCR12 to dstack_pcr_policy() and to the GCP image-identity
    comparison, mirroring AWS_NITRO_TPM_BOOT_PCRS. This changes the quoted selection, so guests and
    verifiers must move together; if the new PCRs enter mr_aggregated then every allowlisted GCP
    measurement has to be re-issued. Worth deciding deliberately: binding them without putting them
    in mr_aggregated (a separate equality check against a value in measurement.gcp.cbor, the way
    AWS's boot_pcr_digest works) gets most of the benefit with a much smaller migration.
  5. Probably not worth it — modelling GCP's TDVF to produce expected MRTD/RTMR0–2. It would mean
    tracking Google's firmware builds, which is the problem (2) exists to avoid. Recording this as
    considered-and-rejected is more useful than leaving it implicit.

5. Dedup

NOVEL. Adjacent, not overlapping:

  • PR #1238 fixes which event log the UKI digest is read from and bounds AIA/CRL fetching. It does
    not change what is compared, and explicitly refutes the stronger "reject an event log with events
    for an unattested PCR" as something that would break every real GCP quote.
  • Issue #1140 covers GCP's fail-open CRL handling, which is a different column of the same matrix.
  • AUDIT-BACKLOG V-c / V-d (os_image_is_dev, os_image_hash anchoring) and PR #1266 cover what the
    response says about the image, not what is compared to produce it.

6. Reachability

  • (1) + (2): the host / cloud operator, within whatever the vTPM lane does not already cover.
    Credential: host control of the VM shape. Frequency: host-controlled. I have not demonstrated any
    concrete substitution — a UKI change is caught by the Authenticode digest, and I did not identify
    something that changes PCR7 or PCR12 without also changing PCR2. Treat the reachability as
    unestablished; the finding is the missing defence, not a demonstrated bypass.
  • (4) EXPECTED_PCR0: nobody triggers it; it is an availability and agility cost borne on Google's
    release schedule.

A full per-platform parity matrix backing this issue — with a file:line or an explicit "nothing",
annotated [platform] or [dstack], in every cell — is in
.agent/TRUST-CHAIN-platform-parity.md.

Lingua principale
Rust
Stelle
546
Fork
96
Merge medio
19h 22m
PR unite (30g)
109

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Dstack-TEE/dstack

Tutte le issue di Dstack-TEE/dstack

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.