Skip to content

Commit 6b9344b

Browse files
committed
docs: explain os_image_hash trust binding
1 parent dcd5bbf commit 6b9344b

4 files changed

Lines changed: 51 additions & 8 deletions

File tree

docs/security/security-model.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,47 @@ dstack implements layered verification from hardware to application. Each layer
9292

9393
**Key management layer.** The KMS root CA public key hash is recorded in RTMR3 as the key-provider event. This binds your workload to a specific KMS instance. The KMS itself runs in a TEE with its own attestation quote, so you can verify the KMS the same way you verify any workload.
9494

95+
### How `os_image_hash` becomes trusted
96+
97+
The `os_image_hash` carried in `vm_config` is not trusted just because the guest
98+
or host reports it. The verifier first validates the hardware-signed quote, then
99+
uses the quoted measurements to bind `os_image_hash` to the software that
100+
actually booted.
101+
102+
For the full-image TDX path, the verifier obtains the OS image identified by
103+
`os_image_hash`, checks the image checksum manifest, recomputes the expected
104+
MRTD and RTMR0-2 from the image and VM configuration, and requires those values
105+
to match the measurements in the quote. If the host substitutes either the image
106+
hash or the VM configuration, the recomputed measurements no longer match the
107+
quote.
108+
109+
For the no-image-download TDX lite path and the AMD SEV-SNP path,
110+
`os_image_hash` is the unified image identity: `sha256(sha256sum.txt)`. The
111+
`sha256sum.txt` file is the image checksum manifest generated at image build
112+
time. It is a text file whose lines contain a SHA-256 digest and relative file
113+
name for each manifest entry, such as `metadata.json`, the kernel, initrd,
114+
firmware, and the split measurement file. Some launch-critical artifacts are
115+
represented indirectly instead of as direct manifest entries: for example, the
116+
rootfs is committed by the measured `dstack.rootfs_hash` kernel command-line
117+
parameter, and the SEV firmware is committed by `measurement.snp.cbor`. The exact
118+
`sha256sum.txt` bytes are hashed, so the manifest contents, file names, ordering,
119+
and line endings are all part of the image identity.
120+
121+
The attestation carries a copy of the image's `sha256sum.txt` plus the platform
122+
specific measurement material (`measurement.tdx.cbor` or
123+
`measurement.snp.cbor`). The verifier checks that:
124+
125+
1. `sha256(checksum_file) == os_image_hash`;
126+
2. the checksum file contains the expected `measurement.*.cbor` entry and that
127+
entry hashes to the supplied measurement material;
128+
3. the supplied measurement material replays to the hardware-signed TDX
129+
MRTD/RTMR values or SEV-SNP launch `MEASUREMENT`/`HOST_DATA`.
130+
131+
Only after these checks pass does the verifier treat the returned
132+
`os_image_hash` as the measured OS image identity. Downstream authorization
133+
systems can then compare that trusted value against an allowlist or governance
134+
contract.
135+
95136
## Verification Checklist
96137

97138
Use this checklist to verify a workload running in a dstack CVM.

dstack-types/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ impl OvmfVariant {
3838
/// Selects how a TDX attestation should bind the OS image.
3939
///
4040
/// `Legacy` preserves the existing verifier behavior: `vm_config.os_image_hash`
41-
/// is the content digest (`digest.txt`) and the verifier recomputes the full
42-
/// TDX launch measurement using the legacy image/QEMU-derived path.
41+
/// is the image digest (`digest.txt`, i.e. `sha256(sha256sum.txt)`) and the
42+
/// verifier recomputes the full TDX launch measurement using the legacy
43+
/// image/QEMU-derived path.
4344
///
4445
/// `Lite` opts into the no-QEMU verifier path: `vm_config.os_image_hash`
4546
/// remains the unified image digest (`sha256(sha256sum.txt)`),

verifier/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,9 @@ The verifier performs three main verification steps:
184184
1. **Quote Verification**: Validates the TDX quote using dcap-qvl, checking the quote signature and TCB status
185185
2. **Event Log Verification**: Replays event logs to ensure RTMR values match and extracts app information. For RTMR3 (runtime measurements), both the digest and payload integrity are verified. For RTMR 0-2 (boot-time measurements), only the digests are verified; the payload content is not validated as dstack does not define semantics for these payloads
186186
3. **OS Image Hash Verification**:
187-
- Automatically downloads OS images if not cached locally
188-
- Uses dstack-mr to compute expected measurements
189-
- Compares against the verified measurements from the quote
187+
- Treats `vm_config` and any attached measurement material as untrusted inputs until they are bound to the hardware quote
188+
- For the full-image TDX path, downloads or loads the image identified by `os_image_hash`, checks the image checksum manifest, uses dstack-mr to compute expected MRTD/RTMR0-2, and compares them against the verified measurements from the quote
189+
- For TDX lite and AMD SEV-SNP, verifies that `os_image_hash = sha256(sha256sum.txt)`, where `sha256sum.txt` is the image build's checksum manifest (`<sha256> <relative-file-name>` lines for image files), that the manifest entry for `measurement.tdx.cbor` or `measurement.snp.cbor` matches the supplied measurement material, and that the measurement material replays to the quote's hardware-signed measurements
190190

191191
All three steps must pass for the verification to be considered valid.
192192

verifier/src/verification.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,13 +742,14 @@ impl CvmVerifier {
742742
};
743743

744744
// Legacy TDX attestation keeps the original KMS verifier semantics:
745-
// os_image_hash must be the image content digest, and expected MRs are
746-
// recomputed through the existing full-image path.
745+
// os_image_hash must be the image digest (digest.txt =
746+
// sha256(sha256sum.txt)), and expected MRs are recomputed through the
747+
// existing full-image path.
747748
let image_paths = self.ensure_image_downloaded(vm_config).await?;
748749
if !Self::image_hash_matches_legacy_digest(&image_paths.image_dir, &vm_config.os_image_hash)
749750
.context("Failed to check legacy image digest")?
750751
{
751-
bail!("legacy TDX attestation requires the digest.txt os_image_hash");
752+
bail!("legacy TDX attestation requires os_image_hash = sha256(sha256sum.txt)");
752753
}
753754
details.os_image_is_dev = Some(image_paths.is_dev);
754755
if !image_paths.version.is_empty() {

0 commit comments

Comments
 (0)