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

Đang mở
#1,289 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
55/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
authentication, security

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Rust
Star
546
Fork
96
Merge trung bình
19 giờ 22 phút
Pull request đã merge (30 ngày)
109

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Dstack-TEE/dstack

Tất cả issue của Dstack-TEE/dstack

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.