Skip to content

chore: sync upstream guest measurement and verifier updates - #3

Merged
lalalune merged 45 commits into
alphafrom
codex/sync-upstream-20260923
Sep 23, 2026
Merged

lalalune merged 45 commits into
alphafrom
codex/sync-upstream-20260923

Conversation

@lalalune

@lalalune lalalune commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Sync upstream Dstack-TEE/dstack@59fee205b5b672448d794824bd1b64624c16b747 into alpha@95fe9395903d748902ef0e38711b024a81202863. Imports 43 new upstream commits without conflicts, preserving the fork’s encrypted-environment manifest support, legacy ciphertext handling, Yocto compatibility and owner-scoped release publishing.

The delta measures encrypted-storage and GPU-claim status, fixes KMS-failover measurement and omitted instance identity handling, orders guest preparation before application startup, bounds Docker startup waits, and corrects verifier certificate validity, measurement caching and event-log documentation. The removed verifier debug response fields are diagnostic; consumers must continue checking quote, app identity and OS-image verification according to the platform-specific contract.

Validation: Rust workspace formatting and merge-diff whitespace checks passed. All seven release build-orchestration and namespace contract tests passed with GNU coreutils (6.557 seconds). Linux-hosted Rust, image and platform checks are required before merge; macOS cannot establish the Linux-only SEV/guest-image behavior. No enclave, KMS policy, encrypted volume or production service is changed by this PR. Hardware acceptance and recovery remain separate requirements.

Full-image follow-up: run35831327742 at0162f776 passed the actual guest-image reproducibility comparison and produced the image archives, but the subsequent module-builder container failed apt dependency resolution. Its floating Debian root had newer libc/SSL/Perl libraries than the pinned July snapshot’s development packages.

Commit54c78ba628cd464da723639f996a7f8477df22d8 pins the module-builder base to the repository’s verified July13 Debian image digest, retains the July21 package snapshot, and checks the suite against the requested release. Build.sh consumes the immutable pin from versions.env. Tracked by elizaOS/eliza#32279 because this fork has issues disabled.

Rust formatting, shell syntax, whitespace and all seven release contracts pass after the fix. Local static acceptance reaches a pre-existing GNU-sed expression unsupported by this macOS environment; the Linux static job remains required. No local Docker daemon is available, so the actual Docker build is delegated to the non-publishing workflow. New full reproducibility run35839398931 at54c78ba6 is pending; do not infer its result from the previous guest-only success. No guest image, KMS policy or production service is deployed.

Final qualification at54c78ba628cd464da723639f996a7f8477df22d8: full image run35839398931 completed SUCCESS. Static contract, guest images and kernel module builder all passed. Guest logs explicitly report reproducibility check passed at2026-09-23T10:20:13Z. Publish release was intentionally skipped; no release tag/publication is claimed. All ordinary PR checks also passed. Native driver build emitted objtool warnings; successful image construction/reproducibility is not proof of supported GPU hardware deployment or protected links. Real TDX/Nitro key release, multi-CVM isolation and recovery acceptance remain separate.

Debian's 90-systemd.preset sorts before 99-dstack-default.preset, so its
enable rules shadowed the `disable *` catch-all and enabled units such as
systemd-sysext and systemd-confext. Remove non-dstack presets in postinst
(mkosi applies presets afterwards) and enable systemd-networkd-wait-online
explicitly, since network-online.target relied on the distro preset.
os-image-hash was extended inside the per-URL request, before the CA cert
was parsed. If that parse failed, the next KMS URL extended it again and
RTMR3 could no longer be replayed. Measure only after a request fully
succeeds, and report every KMS's error instead of just the first.
…olicy

fix(os): drop foreign systemd presets so dstack's policy is terminal
…easure-once

fix(dstack-util): measure os-image-hash once across a KMS failover
The ExecStartPre loop spun forever (TimeoutStartSec=0) once the agent hit
its start limit and was parked in failed, wedging the boot with nothing
reported to the VMM. Exit on is-failed, bound the wait, and report
boot.error to the host via OnFailure.
SystemKeepFree=1G asked for more free space than a 2 GB CVM's /var/log
has, and journald folds keep-free into the limit, so the declared 128M
budget collapsed to the 16M floor. The System* settings were inert anyway:
nothing in the image creates /var/log/journal, so journald has always used
/run/log/journal, where RuntimeMaxUse=0 left it with no budget at all.
…quires-prepare

fix(os): require dstack-prepare and the guest agent before the app
…siduals

docs: explain that gateway_app_id may list several gateway app ids
`storage-fs` is a launch event; `dstack.storage_encrypted` was not, on the
grounds (issue Dstack-TEE#618) that the kernel command line is measured into RTMR2.
That holds on TDX legacy and TDX lite, which both recompute RTMR2 from the
image's base command line, on SEV-SNP, where the command line is hashed
into the launch measurement, and on AWS NitroTPM, where PCR12 is inside the
bound boot_pcr_digest. It does not hold on GCP TDX:
verify_os_image_hash_for_gcp_tdx compares PCR0 and the UKI Authenticode
digest out of the PCR2 event log and nothing else -- no RTMR1/RTMR2, and no
PCR11/PCR12, where systemd-stub measures a command line handed to it rather
than the one inside the UKI.

Emit `storage-encrypted` next to `storage-fs` so the answer rides the one
channel every platform replays. The event ships with a new os_image_hash,
so no deployment sees RTMR3 move without the image moving too.
A relying party cannot tell an omitted instance_id from no_instance_id, so the guest must not skip the check when the host leaves the field out.
…ge-encrypted

feat(dstack-util): measure whether the data disk was encrypted
…eanups

fix(os): give the guest journal a budget it can satisfy
fix(os): bound docker's wait for the guest agent and report failure
kernel.unprivileged_userns_clone is a Debian/Ubuntu kernel patch. The image
builds mainline from kernel.org -- the source whose sha256 versions.env
pins has zero occurrences of the name, while every other key in this file
is there -- and none of the ten dstack patches adds it. systemd-sysctl
logged "Couldn't write '1' to 'kernel/unprivileged_userns_clone', ignoring"
at LOG_WARNING on every boot and exited 0, so the unit succeeded and the
setting did nothing.

Nothing is lost: mainline has no gate, so with CONFIG_USER_NS=y an
unprivileged CLONE_NEWUSER is already allowed, which is all sysbox asks
for.
…-clone-sysctl

fix(os): drop a sysctl the dstack kernel does not have
tboot.service was replaced by dstack-prepare.service in c901eed.
0ff57b2 updated app-compose.service but missed this unit, leaving an
After= on a unit that no longer exists.

The ordering itself still held via Before=dstack-guest-agent.service in
dstack-prepare.service; this makes it visible from the guest agent side too.
`setup_tsm || true` disables `set -e` inside the function, so a failed
`mount -t configfs` fell through to `mkdir` under an unmounted directory
without any warning. Log the failure and return, matching the existing
"configfs not in kernel" case. Boot still continues, since quotes can be
obtained through the ioctl fallbacks.
…s-failure

fix(os): warn and skip TSM setup when configfs mount fails
…ime-log-level

fix(os): stop shipping the NVIDIA container runtime at debug level
…ordering

fix(os): order guest agent after dstack-prepare, not tboot
…-checks

fix(dstack-util): check an omitted MrConfig instance_id as empty
…on-claim-flags

Expose GPU claim status in measured attestation event
`image.verify = false` turns off all OS-image binding at `info!` level, after
which `BootInfo.os_image_hash` is whatever the caller put in
`GetAppKeyRequest.vm_config` - so an `allowedOsImages` allowlist downstream is
gating an attacker-supplied string. `enforce_self_authorization = false` already
warns at startup for a comparable relaxation.

Warn once at startup on the same terms, drop the per-call line to `debug!`, and
report the posture as `GetMeta.os_image_verification` so a relying party can
tell which kind of allowlist decision it is reading. The proto field is optional
and additive: an older KMS leaves it unset, which must not be read as `true`.
No SDK consumes `GetMeta`.
kvinwang and others added 15 commits September 22, 2026 21:41
…fo does not decode

`is_valid` was the literal `true` for any certificate whose RA-TLS attestation
verified, and `decode_app_info(false).ok()` threw the decode error away. A
certificate carrying an attestation that verifies but whose app identity does
not decode printed a valid result with `app_info: null` -- no app_id, no
compose_hash, no os_image_hash -- and exited 0.

Report it the way `/verify` does: `is_valid` plus a `reason`, and a non-zero
exit. The result file is still written first, so a caller inspecting it sees
which check failed.
The field documented itself as verifying RTMR 0-2 digests "through replay
comparison with the quote". Nothing in dstack-verifier or dstack-attest replays
a boot-time event log: the flag is set once the runtime event log replays to
its register and the app identity decodes out of its payloads. A TDX quote's
RTMR 0-2 entries reach only the `--debug` diff and the three named ACPI digests
the lite path cross-checks.

Correct the comment rather than the code. Replaying RTMR 0-2 would add nothing:
those registers are already checked against measurements recomputed from the OS
image, which does not trust the host's event log at all, and is the stronger of
the two checks.
…fy-visibility

fix(kms): make a disabled os image verification visible
The debug request flag only fed a development-time RTMR diff
(rtmr_debug) and an ACPI table dump (acpi_tables) on the legacy TDX
path. The diff was never wired to the event log, so it always reported
the whole expected sequence as missing. Nobody relies on it, so drop the
flag, both response fields and the code behind them instead of fixing it.

VerificationRequest does not deny unknown fields, so clients still
sending debug keep working.
With compare_tdx_mrs gone, the private Mrs struct only mirrored
dstack_mr::TdxMeasurements field by field. Compare TdxMeasurements
directly and share the TD report extraction between the legacy and lite
paths.

TdxMeasurementDetails.acpi_tables was only read by the removed debug
dump, so drop it and let Tdvf::rtmr0_log return just the log.
…-rtmr-diff

verifier: remove the debug verification path
…y-cert-validity

fix(verifier): stop reporting --verify-cert results valid when app info does not decode
vm_config_cache_key hashed the whole VmConfig, including fields the full-image
measurement never reads: an arbitrary `image` string, and the
`tdx_measurement`/`gcp_measurement`/`aws_measurement` documents, each carrying
a caller-sized checksum_file and CBOR blob. A cache miss costs a full firmware
and kernel hash plus ACPI generation and leaves a file under
<cache>/measurements/ that nothing evicts, so one captured quote replayed with
a different filler byte per request misses every time and grows the cache
without bound.

Clear those four before hashing. Clearing rather than listing the fields that
matter keeps a VmConfig field added later in the key by default, which is the
safe direction: an extra miss, never a stale measurement.
compute_measurements hashes the firmware and the kernel and generates the
ACPI tables -- pure CPU over a multi-hundred-megabyte image -- and was called
synchronously from async fns, so it parked a Rocket worker for the whole
computation. tpm-qvl already fetches collateral through spawn_blocking for the
same reason.

Pass ImagePaths instead of its fields one by one, and run the measurement
through spawn_blocking. The cache lookup and store stay on the async side:
they are small file operations.
SEV-SNP has no runtime event log (identity comes from mr_config via
HOST_DATA), Nitro Enclave and GCP TDX were missing, and the replay runs
during quote verification, so a mismatch fails quote_verified. The
--debug diff does not read event log entries, and the lite ACPI digest
check is fatal rather than diagnostic.
…rement-cache

fix(verifier): key the measurement cache on the measured VM shape only
…re-off-runtime

refactor(verifier): measure the OS image on a blocking thread
…t-log-verified

docs(verifier): say what event_log_verified actually covers
@lalalune
lalalune merged commit b7acc08 into alpha Sep 23, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants