sign_csr stamps an app-info certificate extension whose device_id and os_image_hash the issuer never verified
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 55/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- rust
- Ambito
- authentication, security
Direzione di ricerca
Start in ra-tls/src/cert.rs at CaCert::sign_csr and compare its callers with kms/src/main_service.rs and the guest-agent call site. Trace decode_app_info_ex and the verified AppInfo path in dstack-attest/src/attestation.rs, then add a regression test showing that rewriting attestation.config cannot change the KMS-stamped extension; done means the KMS uses verified AppInfo while the local CA retains current behavior.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Label: BUG (demonstrated) — mechanism reproduced by running it. End-to-end exploitation through a live KMS is argued from source, not demonstrated (no TDX hardware here).
Base: origin/next @ 030fbb2183. All cited files are byte-identical to origin/next.
What the chain claims
ra-tls/src/attestation.rs:12-18:
Application identity asserted by a KMS-issued certificate is an issuer claim and must be consumed only after normal certificate-chain verification.
The reader's contract is: once the chain verifies, the fields in the PHALA_RATLS_APP_INFO extension (OID 1.3.6.1.4.1.62397.1.9) are what the issuer verified.
What it enforces
ra-tls/src/cert.rs:99-103:
let app_info = if cfg.ext_app_info {
Some(csr.attestation.clone().into_v1().decode_app_info(false)?)
} else {
None
};
decode_app_info here is AttestationV1::decode_app_info_ex (dstack-attest/src/attestation.rs:917-1026) — the decoder that runs before verification. Two of its fields are not the ones the issuer checked:
device_id—dstack-attest/src/attestation.rs:945is literallydevice_id: sha256(Vec::<u8>::new()).to_vec(), i.e. the constante3b0c442…, identical for every guest on every platform. The verified decoder at:2003usessha256(PPID)recovered from the DCAP-validated PCK chain.os_image_hash—:990-993reads it out ofself.stack.config(), the free-formconfigstring the attestation carries. Nothing inverify_with_time(:2336-2407) inspectsconfig. The KMS separately verifiesrequest.vm_config, which is a different field (kms/src/main_service.rs:363-369and:323-324).
cfg.ext_app_info lives inside the CSR and is chosen by the requester (ra-tls/src/cert.rs:156). It is set in production by dstack-util/src/system_setup.rs:613 and :632 (the gateway registration certificate) and by guest-agent/src/rpc_service.rs:512 (ext_app_info: request.with_app_info).
The right pattern is in the same statement — kms/src/main_service.rs:564-566:
let cert = app_ca.sign_csr(&csr, Some(&app_info.boot_info.app_id), "app:custom")?;
PHALA_RATLS_APP_ID gets the verified app_id. PHALA_RATLS_APP_INFO gets a fresh unverified re-decode.
Demonstrated
Scratch crate outside the repo, path-depending on ra-tls and cc-eventlog, driving the checked-in sdk/simulator/attestation.bin through CaCert::sign_csr(..., ext_app_info: true) and reading the extension back with CertExt::get_app_info() — the same call ra-rpc/src/rocket_helper.rs:524 makes. Only attestation.stack.config was rewritten; the quote and the runtime events (and therefore the RTMR3 replay and the report_data binding) were left untouched.
--- unmodified simulator attestation ---
app_id = 5bb4ff9a3837357f19dc176407a5709c62eb6c56
compose_hash = c143116fef3940e7f776bfc0919c256232ae5338a6e948c96b711e0b2383483a
os_image_hash = e61be43dd1cc6a6f5edefd7c51b608983780839b0000ce60dd99737dcffe09b9
device_id = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
--- attestation.config rewritten ---
app_id = 5bb4ff9a3837357f19dc176407a5709c62eb6c56
compose_hash = c143116fef3940e7f776bfc0919c256232ae5338a6e948c96b711e0b2383483a
os_image_hash = deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
device_id = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Not demonstrated: that a production KMS signs such a CSR. That needs a TDX quote over sha512("ratls-cert:" || SPKI) for a key the requester holds, and there is no TDX hardware on this machine. The argument is that sign_cert reads cfg.ext_app_info straight off the attacker's CSR (cert.rs:98-99) and that verify_with_ra_pubkey never looks at config.
Reachability
- Who can trigger it: any tenant able to obtain a TDX quote bound to a key it holds — i.e. anyone with a deployed dstack app, via
Tappd.TdxQuote/IssueCerton its own guest agent. - What credential it needs: a registered app and a whitelisted compose hash. No operator or KMS access.
- Who controls frequency: the requester, unbounded.
What an attacker gains
A KMS-signed certificate asserting an os_image_hash the KMS never verified, plus a device_id that is a constant.
The reachable consumer is ra-rpc/src/rocket_helper.rs:523-524 → gateway/src/main_service.rs:2686-2694, which prefers the certificate extension over the live attestation, → gateway/src/main_service/auth_client.rs:23-33, which POSTs the whole AppInfo to a policy URL. Gateway auth ships enabled = false (gateway/gateway.toml:36-37), so in the default configuration there is no deployed decision behind those fields. That is why this is filed as a design issue rather than an advisory — but the extension is documented as an issuer claim and is exactly the thing a future policy backend would read.
(The gateway's preference for the extension over live attestation at main_service.rs:2686-2694 was introduced by 92f61ecf66 "gw: Support for non-quote RATLS". It is the same shape as GHSA-9fcv-r8wv-c65j, which is marked closed; the code still does it. Noted here as the reachability context, not as a separate ask.)
Steelman
sign_csr is a CaCert method shared by the KMS and by the guest agent's local CA. Only the KMS has a verified AppInfo; the local CA has none, and a CaCert signature cannot take a parameter only one caller can supply without changing the type. Re-decoding is the obvious way to make one function serve both, and cert-client/src/lib.rs:26-47 spells out that exact reasoning for the app_id half — correctly, because there the CSR and the CA are the same principal. The problem is generalising it to the KMS case, where they are not.
device_id has a second, independent justification: the PPID is not in the TD report, it is recovered from the PCK certificate during quote verification, so a guest genuinely cannot compute its own. Returning sha256("") keeps the struct total. Publishing it in a certificate is what turns a placeholder into an assertion.
Improvement directions
- Cheap, no wire change, matches the existing convention. Give
sign_csran optional verified&AppInfoand prefer it when present, mirroring theapp_id: Option<&[u8]>parameter already in the signature. The KMS passesapp_info.boot_info; the local CA passesNoneand keeps today's behaviour. One parameter, two call sites, plus a test asserting that rewritingattestation.configno longer changes the stamped extension. - Cheap, narrower, composable with (1). Omit
device_idfrom the extension when it is the unverified constant rather than publishingsha256("")as an identity. Consumers already tolerate absent fields — the msgpack-named encoding atcert.rs:385-392exists for that. - Deployment event — do not start here. Stop carrying
AppInfoin a certificate and make consumers read the attestation extension and verify it. That is wherera-tls/src/attestation.rs:12-18andra-rpc/src/ratls_client_verifier.rs:14-22already point, but it changes what every gateway and every peer reads, and needs a migration acrossgateway/,dstack-util/and the SDKs.
(1) is the one that fits the tree's conventions and is testable without hardware.
- 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
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Dstack-TEE/dstack
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 30/100
Dstack-TEE/dstack#1301 ·
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
Dstack-TEE/dstack#1300 ·
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
Dstack-TEE/dstack#1299 ·
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
Dstack-TEE/dstack#1298 ·
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
Dstack-TEE/dstack#1297 ·
Tutte le issue di Dstack-TEE/dstack
Issue simili
-
risk:low runtime status:in-progress type:test
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 92/100
zeroclaw-labs/zeroclaw#11023 ·
-
good first issue refactor
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
EricSpencer00/Resilient#4835 · 1 commento ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
bisq-network/bisq-musig#204 ·
-
agent:ready documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
cesarferreira/stax#890 ·