Finding 6/6: Can you trust what you audit?
Event Log Semantic Content Forgeable for IMR 0-2 While Reported as "Verified"
Severity: High (reported), resolved as documentation/API-semantics issue
Affected component: cc-eventlog crate, dstack verifier, dstack KMS
The dstack verifier accepted client-supplied event logs, replayed their cryptographic digests against the Intel-signed quote, and emitted event_log_verified: true. For three of four RTMR lanes (IMR 0, 1, and 2), the human-readable fields in those event logs were never checked against the digests. An attacker could forge the semantic content of boot-time events while keeping the digests intact, and the system would report the result as verified.
The issue was identified by Bluethroat Labs and responsibly disclosed to Phala Network in January 2026.
Executive Summary
- The
TdxEventLog::validate()function explicitly skipped semantic validation for IMR 0, 1, and 2. Only IMR 3 events had their human-readable fields checked against the cryptographic digest. - The RTMR replay logic used only the
digestfield for hash-chain computation. Theevent,event_type, andevent_payloadfields were not consumed by the replay. - After successful replay, the verifier set
event_log_verified: truewith no distinction between "digests match the quote" and "semantic fields match the digests." - An attacker with access to a legitimate quote could forge the semantic content of IMR 0-2 events while preserving digest integrity, producing output that passed all verification checks.
- No exploitation of this vulnerability was observed during the assessment. The scenarios described below represent damage that was possible given the code as written, not damage that occurred.
Background
Intel TDX maintains four Runtime Measurement Registers (RTMR 0-3), each accumulating a hash chain of events that occurred during different boot and runtime phases:
- RTMR 0 (IMR 0): firmware and BIOS measurements
- RTMR 1 (IMR 1): OS loader and kernel measurements
- RTMR 2 (IMR 2): OS and application-layer measurements
- RTMR 3 (IMR 3): runtime application events (used by dstack for app identity)
Each event in the log contains two categories of data: a cryptographic digest (the 48-byte SHA-384 hash that feeds the RTMR chain) and human-readable semantic fields (event_type, event, event_payload) that describe what the measurement represents.
The digest is what the hardware cares about. It is extended into the RTMR and eventually signed by Intel in the TDX quote. The semantic fields are what humans and downstream systems care about. They tell you that a particular digest corresponds to, for example, a specific kernel version or a particular boot configuration.
The security property that matters here: the semantic fields should be bound to the digest. If they are not, an attacker can substitute arbitrary semantic content while the digests remain valid, and the verification output will describe a boot configuration that never existed.
Why the Event Log Is Central to dstack's Value Proposition
dstack's entire positioning is built on the principle that users should not need to trust the infrastructure. They should be able to verify it themselves.
The dstack framework documentation states that "users...must be able to verify all aspects of that application, including network setup, application identity and code logic, underlying hardware, and execution environment." The project's tagline is "don't trust, verify." The dstack.org homepage describes the system as providing "verifiable job logs" so that "every AI job can be proven to run securely and untampered." Every deployed application comes with what dstack calls a "comprehensive Trust Center for full verification."
The event log is the component that makes this verification promise concrete. Without it, a user looking at attestation output sees opaque 48-byte SHA-384 hashes. Those hashes are cryptographically valid, but they are not human-interpretable. The event log is what translates 0xABCD... into "this firmware booted, this kernel loaded, this configuration was applied." It is the bridge between cryptographic attestation and the human-readable transparency that dstack sells as its differentiator.
At the time of the assessment, the verifier's README listed "Event Log Verification" as one of three core verification steps, describing it as "Replays event logs to ensure RTMR values match and extracts app information." No distinction between RTMR lanes. No caveat that IMR 0-2 semantics were unverified. The documentation has since been updated to clarify this (the current README now states that for RTMR 0-2, "only the digests are verified; the payload content is not validated"), which confirms that the original documentation did not make the distinction.
This context matters for severity. A forgeable interpretation layer in a system that does not claim to be human-verifiable is a correctness issue. A forgeable interpretation layer in a system whose core business promise is "you can verify everything yourself" is a different class of problem.
Vulnerability Details
The Validation Skip
The TdxEventLog structure contained both the digest and the semantic fields:
// cc-eventlog/src/lib.rs
pub struct TdxEventLog {
pub imr: u32,
pub event_type: u32,
pub digest: [u8; 48],
pub event: String,
pub event_payload: Vec<u8>,
}The validate() method was intended to check that the semantic fields hash to the stored digest. It did this for IMR 3. For IMR 0, 1, and 2, it returned success without checking anything:
// cc-eventlog/src/lib.rs, lines 90-100
pub fn validate(&self) -> Result<()> {
if self.imr != 3 {
// TODO: validate other imrs
return Ok(()); // No validation for IMR 0, 1, 2
}
let digest = event_digest(self.event_type, &self.event, &self.event_payload);
if digest != self.digest {
return Err(anyhow::anyhow!("invalid digest"));
}
Ok(())
}The // TODO: validate other imrs comment indicated this was a known gap in the implementation.
The Replay Logic
The replay_event_logs() function in attestation.rs called validate() and then used only the digest field for RTMR computation:
// ra-tls/src/attestation.rs, lines 434-442
for event in eventlog.iter() {
event
.validate()
.context("Failed to validate event digest")?; // Returns Ok(()) for IMR 0-2
if event.imr == idx {
let mut hasher = Sha384::new();
hasher.update(mr);
hasher.update(event.digest); // Only digest is used, not semantic fields
mr = hasher.finalize().into();
}
}The replay verified that the chain of digests reproduced the RTMR values in the Intel-signed quote. That is a real cryptographic check. But it only proved that the digests were correct. It said nothing about whether the semantic fields matched those digests.
The "Verified" Label
After successful replay, the verifier set:
// verifier/src/verification.rs, line 408
details.event_log_verified = true;This single boolean collapsed two distinct properties into one signal:
- "The digest chain replays correctly against the quote's RTMR values" (true, cryptographically verified)
- "The semantic content of the event log is authentic" (true for IMR 3, unchecked for IMR 0-2)
A consumer reading event_log_verified: true had no way to distinguish between these two properties. The API did not separate them.
The Trust Boundary
The event log was client-supplied. Both the verifier and the KMS accepted event logs from the network as part of the attestation request:
POST /verify { quote, event_log } // Verifier
POST /sign-cert { quote, event_log } // KMSThe event log then flowed into BootInfo and was passed to the authorization hook:
// kms/src/main_service.rs
let boot_info = BootInfo {
// ... fields populated from event log ...
};
let response = self
.state
.config
.auth_api
.is_app_allowed(&boot_info, is_kms)
.await?;This meant forged semantic content from IMR 0-2 could reach the authorization boundary with a "verified" label attached.
The Attack
The attack was straightforward. An attacker with access to a legitimate TDX quote could:
- Obtain a valid
{ quote, event_log }pair from a real TDX VM - Modify the semantic fields (
event,event_type,event_payload) for any IMR 0-2 event - Leave the
digestfields unchanged - Submit the modified event log alongside the original quote
The result:
Attacker obtains legitimate { quote, event_log } from a real TDX VM
|
v
Modifies IMR 0-2 events: keeps digest[], forges event/event_type/event_payload
|
v
POST /verify or KMS /sign-cert with { quote, forged_event_log }
|
v
TdxEventLog::validate() returns Ok(()) for IMR 0-2
|
v
RTMR replay uses only digest[] -> matches quote's RTMRs
|
v
event_log_verified = true
app_info populated from forged semantic fields
|
v
Output contains forged boot configuration data labeled as "verified"The attacker did not need to break any cryptographic primitive. They did not need to find hash preimages. They only needed to replace the human-readable description of what was measured while leaving the measurements themselves untouched.
What This Did Not Allow
The digest chain itself was cryptographically sound. An attacker could not forge the RTMR values in the quote, alter what was actually measured during boot, or break the binding between the digests and the Intel-signed attestation. The hardware measurements were real and verified.
What was forgeable was the interpretation layer: the human-readable description of what those measurements meant. The system told consumers "this event log is verified" without distinguishing between "the measurements are authentic" and "the descriptions of those measurements are authentic."
The Disclosure Conversation
This finding went through multiple rounds of discussion during the disclosure process.
We reported it as High severity based on the forgeable semantics and the misleading event_log_verified: true signal. The team's position was that IMR 0-2 event log semantics were not verifiable in the same way IMR 3 was, that dstack did not consume IMR 0-2 logs for security decisions, and that no user applications relied on them.
Our response acknowledged that if IMR 0-2 semantics were genuinely not verifiable and not consumed, the primary issue shifted from cryptographic bypass to API semantics and developer interpretation risk. However, we maintained that the system needed to either:
- split the verification signal into "RTMR replay verified" vs. "semantic payload verified," or
- mark IMR 0-2 as "digest-only / semantics unverified" and stop labeling the result in a way that created a false assurance boundary.
The core of our position: we were not asking the team to validate what was infeasible. We were asking the system to stop reporting a single event_log_verified: true when semantic verification only covered one of four RTMR lanes.
The team accepted this as a documentation-clarity issue and committed to:
- Documenting precisely what
event_log_verified: truesignifies - Removing IMR 0-2 event logs in the next version to eliminate the ambiguity
We closed the issue after receiving confirmation of these commitments.
Anyone who wishes to review the full timeline of the disclosure process is free to examine the public thread at x.com/saxenism/status/2021943382155899351.
Why This Matters
The technical severity of this finding depends on how you scope it.
If you scope it narrowly as "can an attacker bypass cryptographic attestation," the answer is no. The RTMR replay was sound. The digests were verified against the Intel-signed quote.
If you scope it as "can an attacker cause the system to emit verified attestation output that misrepresents the boot configuration," the answer is yes. The event_log_verified: true signal did not distinguish between digest-level and semantic-level verification, and three of four RTMR lanes had no semantic binding.
But the real weight of this finding sits in what dstack promises its users.
dstack does not position itself as a low-level attestation library for specialists. It positions itself as a platform where non-expert users can verify that their workloads ran securely. The Trust Center exists so that a business customer, a compliance officer, or an auditor can look at the verification output and make a trust decision. The event log is the only component of that output that translates raw measurements into something a human can read and reason about.
When three of four RTMR lanes have forgeable semantic content and the API reports event_log_verified: true without distinction, the verification output that a Trust Center consumer sees can describe a boot configuration that never existed. The consumer has no way to detect this. They are not expected to replay hash chains themselves. They are expected to read the verified event log and trust it, because that is what the platform told them they could do.
This is not a hypothetical consumer expectation. It is the expectation dstack's own documentation creates. The framework documentation says users "must be able to verify all aspects of that application, including...underlying hardware, and execution environment." The verifier README listed event log verification as a core step. The Trust Center was described as providing "full verification." A consumer reading these documents and seeing event_log_verified: true would reasonably conclude the event log contents were trustworthy across all RTMR lanes. For IMR 0-2, they were not.
This class of issue shows up whenever a verification system collapses multiple assurance levels into a single boolean. The fix is not always more cryptography. Sometimes it is more precise labeling. But when the product's differentiator is "you can verify everything yourself," the labeling is the product.
Severity Classification
We reported this as High severity. The High classification was not based on cryptographic bypass. It was based on the gap between what the verification output claimed and what it actually guaranteed, in the context of a platform whose core value proposition is user-verifiable transparency.
dstack is not a low-level library where consumers are expected to understand the distinction between digest replay and semantic validation. It is a platform that promises "full verification" through a Trust Center designed for business customers. When that platform emits event_log_verified: true and the human-readable content of 75% of the measurement lanes is forgeable, the verification promise that dstack sells is undermined at the interpretation layer, which is the only layer most of its target users interact with.
Through the disclosure conversation, the team's position was that IMR 0-2 semantics were not consumed for security decisions and that no user applications relied on them. We acknowledged that this narrowed the immediate exploitation risk, but maintained that the API semantics created a false assurance boundary that downstream consumers could not detect.
The team committed to documenting the verification semantics and removing IMR 0-2 logs in the next version. We closed the issue after receiving that commitment. The verifier README has since been updated to explicitly state that RTMR 0-2 event logs receive digest-only verification.
Fix
The agreed resolution had two parts:
-
Documentation: clarify what
event_log_verified: truemeans (specifically: "digest replay matches RTMR values in quote," not "semantic fields validated across all lanes"). -
Removal: in the next version, remove IMR 0-2 event logs from the response schema entirely, eliminating the ambiguity. If they are retained for informational purposes, mark them clearly as unverified and ensure client libraries and documentation do not imply they are authenticated.
For systems that need to retain IMR 0-2 event logs, an alternative approach would be to extend validate() to all IMR lanes (the event_digest() function already exists) and split the verification response into per-lane or per-category signals rather than a single boolean.
Timeline
| Date | Event |
|---|---|
| January 6, 2026 | Finding reported to Phala Network |
| January 2026 | Multiple rounds of discussion on severity and scope |
| January 2026 | Team commits to documentation fix and IMR 0-2 removal in next version |
| January 2026 | Issue closed after resolution confirmation |
| February 2026 | Phala publishes security update blog |
Key Takeaway
A verification API that emits a single "verified" boolean for a process that has multiple assurance levels is making a claim it cannot support. If digest replay covers all four RTMR lanes but semantic validation only covers one, the output needs to reflect that distinction. Collapsing "digests match" and "semantics are authentic" into one signal creates a false assurance boundary that downstream consumers have no way to detect.
References
- Original assessment finding:
code/submitted_findings.mdfinding#2 - Public disclosure thread: x.com/saxenism/status/2021943382155899351
- dstack framework documentation: https://phala.com/posts/Dstack-A-Zero-Trust-Framework-for-Confidential-Containers
- dstack attestation guide:
https://github.com/Dstack-TEE/dstack/blob/master/attestation.md - dstack homepage: https://dstack.org
This vulnerability was discovered by Rahul Saxena of Bluethroat Labs during a security assessment of the dstack ecosystem. Responsible disclosure was coordinated with Phala Network.
