DEV Community

Cover image for The Attestation Freshness Paradox: Why Your TEE's Proof Expires the Moment It's Issued
crow
crow

Posted on Edited on

The Attestation Freshness Paradox: Why Your TEE's Proof Expires the Moment It's Issued

A few months ago I wrote about the MRENCLAVE Paradox — the fact that Intel SGX's cryptographic fingerprint of your enclave code doesn't see everything. If you load a scripting engine or a policy module at runtime, you can swap that inner logic completely, and MRENCLAVE won't move an inch. It's a real fingerprint. It's just checking the wrong layer.

This week, explaining a different project out loud, I ran into the sequel to that problem. It's not about nested sandboxes this time. It's about time itself.

The question that started it

I was walking someone through a TEE/HSM architecture — a trusted execution environment and a hardware security module that shake hands once, agree on a key, and then never talk to each other again for the rest of the session. It's a deliberate design: no network round trip on the hot path, ever again, after that first handshake.

The question was simple: "Does the HSM ever check the TEE's code again, after that first handshake?"

I said no. Then I sat with why not.

PCR0 has the same blind spot as MRENCLAVE — just a different flavor

AWS Nitro Enclaves use PCR0 the way SGX uses MRENCLAVE: a hash of your enclave's code, computed once, at boot. Ask the Nitro Security Module for a fresh attestation document five minutes later — same process, still running — and you get the same PCR0 back. Nothing re-measures the code after launch. Nothing can, in the way people usually imagine it.

Here's what that means concretely. Say the code inside the enclave gets altered live, in memory, without a restart — a side-channel attack like Rowhammer, which flips bits electrically rather than through a normal write instruction, bypassing the access checks that would normally stop it. The measurement value never updates. It still reports the old, "good" code. Every key derivation, every signature, everything built on top of that measurement keeps running exactly as before — completely unaware anything happened.

This isn't an AWS bug or an Intel bug. It's true of every mainstream TEE I know of. Boot-time attestation gives you a strong guarantee about what launched. It gives you nothing, by default, about whether that's still true an hour into runtime.

Why the old fix doesn't cover this one

My SGX piece solved a real problem with signed policy manifests: have the enclave sign a hash of whatever policy/logic is currently active, and bind that signature into every output, so a remote verifier can check which version actually ran — instead of trusting a boot-time fingerprint that never sees runtime-loaded logic.

That fix works because it assumes the code doing the signing is still honest. In the nested-sandbox case, it is — the interpreter itself was never touched, only the data (the policy) fed into it. The interpreter faithfully reports what it's running, and that report can be trusted.

Rowhammer-style tampering breaks that assumption. If the code responsible for signing the "here's what's active" manifest is itself the thing that got corrupted, its signed report is worthless — it can lie about anything, including its own policy hash. Same hardware key, same signature format, zero remaining guarantee. Different failure mode, same underlying vulnerability, and it needs a different fix.

The naive fix, and why it's circular

The obvious next idea: why not just hash the running code, right now, live, and check that?

It doesn't work, and it's worth being precise about why. Whatever component computes that fresh hash and reports it back is running on the exact machine you're trying to verify. If that machine is already compromised, the "honest self-report" is exactly the thing you can no longer trust. You haven't added a witness. You've asked the suspect to vouch for himself.

This is the same shape of problem that shows up in distributed systems as the "long-range attack" — a verifier shown a plausible-looking but incomplete or falsified history has no way to know, from the data alone, that something's missing or wrong. You can't out-clever it with a smarter check running inside the same trust boundary you're questioning.

The actual fix is boring, and that's the point

Force the enclave to restart on a schedule. Check a timestamp that only genuine hardware can sign — not something the software reports about itself.

This matters because Nitro's attestation document already carries a timestamp field, generated and signed by the NSM hardware module at request time, not computed or asserted by the application. A verifier — the HSM, or an external anchor — can refuse to accept further activity from a session whose bootstrap attestation is older than some maximum session age. That forces an actual process restart: a real boot, a freshly-computed PCR0, a new session key. Not a same-process requote that returns the same old measurement wearing a new timestamp.

It doesn't prove the code never changed. It bounds how long a compromise, once it happens, can go unnoticed — to at most one restart interval. That's a meaningfully different claim than "we caught it," and it's worth saying so plainly rather than overselling it.

It isn't free. A forced restart means a real window with no valid session unless you handle it gracefully — rolling restarts across multiple instances, or accepted brief downtime for a single one. That's an availability trade-off, not a footnote.

Detecting a real restart

A timestamp alone doesn't prove an enclave actually restarted — it only proves the clock moved. So I asked AWS and Intel directly: is there a hardware-level way to tell that a fresh instance actually launched, not just that time passed?

AWS Nitro's attestation document has a module_id field, assigned by the hypervisor at enclave creation — not something code inside the enclave can choose. AWS's own docs don't state outright that it's guaranteed unique on every relaunch, so I asked them to confirm. No response yet.

While I had real Nitro hardware up anyway, I checked module_id myself: launched the same enclave image three separate times, got three different module_id values each time. Not proof — three samples isn't a guarantee — but real evidence gathered on our own hardware while we wait on AWS to confirm it officially.

Intel's answer (Intel Community forum, 2026-09-10): SGX and TDX have similar fields (CONFIGID/CONFIGSVN on SGX, MRCONFIGID/MROWNER/MROWNERCONFIG on TDX), but they're software-defined — whoever launches the enclave has to explicitly set a fresh value each time. Nothing enforces it automatically the way it appears to on Nitro.

The pattern, stated plainly

Attestation is a photograph, not a live camera feed. Every mainstream TEE takes that photograph once, at boot, and hands it to you as if it still describes the room. Most of the time it does. The fix isn't a cleverer photograph. It's admitting the photograph has an expiration date, and checking that date against a clock the room itself can't touch.

It doesn't solve trust. It just puts a limit on how long you can lose it before someone notices.

Top comments (0)