sign_csr stamps an app-info certificate extension whose device_id and os_image_hash the issuer never verified

オープン
#1,289 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
rust

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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_iddstack-attest/src/attestation.rs:945 is literally device_id: sha256(Vec::<u8>::new()).to_vec(), i.e. the constant e3b0c442…, identical for every guest on every platform. The verified decoder at :2003 uses sha256(PPID) recovered from the DCAP-validated PCK chain.
  • os_image_hash:990-993 reads it out of self.stack.config(), the free-form config string the attestation carries. Nothing in verify_with_time (:2336-2407) inspects config. The KMS separately verifies request.vm_config, which is a different field (kms/src/main_service.rs:363-369 and :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 / IssueCert on 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-524gateway/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

  1. Cheap, no wire change, matches the existing convention. Give sign_csr an optional verified &AppInfo and prefer it when present, mirroring the app_id: Option<&[u8]> parameter already in the signature. The KMS passes app_info.boot_info; the local CA passes None and keeps today's behaviour. One parameter, two call sites, plus a test asserting that rewriting attestation.config no longer changes the stamped extension.
  2. Cheap, narrower, composable with (1). Omit device_id from the extension when it is the unverified constant rather than publishing sha256("") as an identity. Consumers already tolerate absent fields — the msgpack-named encoding at cert.rs:385-392 exists for that.
  3. Deployment event — do not start here. Stop carrying AppInfo in a certificate and make consumers read the attestation extension and verify it. That is where ra-tls/src/attestation.rs:12-18 and ra-rpc/src/ratls_client_verifier.rs:14-22 already point, but it changes what every gateway and every peer reads, and needs a migration across gateway/, dstack-util/ and the SDKs.

(1) is the one that fits the tree's conventions and is testable without hardware.

主要言語
Rust
スター
546
フォーク
96
平均マージ
19時間 22分
マージ済み PR(30日)
109

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Dstack-TEE/dstack のほかの issue

Dstack-TEE/dstack の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。