jouleclaw-attest-vendor: read TDX and SNP fields at their published offsets #389

Merged
dcharlot merged 1 commit from fix/attest-vendor-offsets into main 2026-10-21 10:19:10 -04:00

2026-10-21

Transaction Science
jouleclaw-attest-vendor: read TDX and SNP fields at their published offsets
All checks were successful
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m9s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 9s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m22s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m3s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 2m20s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m55s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m39s
The parser read the wrong bytes for most of the fields it emits. Its test
fixtures were built from the same constants, so nothing could catch it:

TDX:
- The header is 48 bytes, not 52; qe_vendor_id sits at byte 12.
- The TD 1.0 report body is 584 bytes, not 528.
- mrtd is at body+136, not body+0, which is TEE_TCB_SVN and MRSEAM.
- rtmr0..3 are at body+328/376/424/472, not 168/216/264/312.
- report_data is at body+520, not body+376, which is RTMR1.
- A v5 quote is not "backward-compatible on the header fields": a 6-byte
  body descriptor (type, size) comes before its body.

SNP:
- The launch measurement is at 0x090, not 0x130, which lies inside
  author_key_digest.
- id_key_digest is at 0x0E0, not 0x160, which is report_id_ma.
- report_data at 0x050 and host_data at 0x0C0 were already right.

Every offset is the one sandbox-tee's verifier uses, and agrees with the
published layouts.

The parser now also refuses a quote whose tee_type is not TDX (0x81), and a
v5 quote whose body is not a TD report (type 2, 584 bytes, or type 3,
648 bytes, with a matching size), rather than reading them at TD offsets.

The fixtures are rewritten from literal published offsets, independent of
the parser's constants. Every field gets its own marker, with decoys at the
offsets that were read before. The vec_init_then_push pattern and a
module-doc list item that clippy flagged are fixed in the same functions;
the doc no longer claims a tcb_svn field it never emitted.

No crate in the tree depends on this one, so the defect was latent.

Tests: 12. Mutation testing: 13 mutants, all killed. Six put back an old
offset, which is exactly the code this replaces. The other seven remove the
tee-type check, the v5 descriptor and size checks, the TD 1.5 length, v5
support, or the short-body check.

Verified on the build host: clippy -D warnings and rustdoc -D warnings
clean on jouleclaw-attest-vendor.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 07:29:25 -04:00