fix(tpm2): honor gotpm:"check" tag in the unmarshaller#435
Open
evilgensec wants to merge 1 commit into
Open
Conversation
`TPMSAttest.Magic` carries `gotpm:"check"` to declare that the field's `TPMGenerated.Check()` (which enforces `== TPM_GENERATED_VALUE`) must run during unmarshalling. The reflection-based unmarshaller in tpm2/reflect.go processes every other tag (`skip`, `list`, `sized`, `sized8`, `tag`, `nullable`, `optional`, `handle`, `auth`, `bit`, `anon`) but never inspects `check`, so the tag is silently ignored and `TPMGenerated.Check()` has zero call sites in the package. A verifier following the pattern in tpm2/test/certify_test.go therefore returns a populated `TPMSAttest` for blobs whose Magic is anything -- the integrity boundary between "TPM-generated attestation" and "arbitrary bytes signed by any key the verifier trusts" disappears. Wire `check` into `unmarshalStructField`: after the field's bytes are read, if the field type implements `Check() error`, call it and return the error. Adds tpm2/check_tag_test.go with a regression matrix that asserts Unmarshal[TPMSAttest] rejects Magic=0x00000000, 0xdeadbeef, and the byte-swapped lookalike 0x47ff5443, while still accepting the legitimate 0xff544347. The new tests don't require the TPM simulator dep.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TPMSAttest.Magic(tpm2/structures.go:1318) is declared withgotpm:"check". The tag is meant to declare that the field's type implementsCheck() errorand that the unmarshaller must reject a blob whose value fails the check.TPMGenerated.Check()(tpm2/structures.go:75-82) enforcesg == TPMGeneratedValue(0xff544347).unmarshalStructFieldintpm2/reflect.goprocesses every othergotpm:tag (skip,list,sized,sized8,tag,nullable,optional,handle,auth,bit,anon) but never inspectscheck. Result:TPMGenerated.Check()has zero call sites anywhere in the package, the Magic field is the only field carrying the tag, andUnmarshal[TPMSAttest]happily returns a populated struct for any 4-byte Magic value.A verifier following the pattern in
tpm2/test/certify_test.go(lines 138-167) — call.Contents()to unmarshal the attest, marshal it back to compute the digest, verify the signature with the trusted AK, readAttested.Quote().PCRDigestand trust the result — therefore accepts forged "quotes" whose Magic is anything, as long as the surrounding signature verifies. The Magic field is precisely the boundary between "TPM-generated attestation" and "arbitrary bytes signed by any key the verifier trusts". Real-world signing oracles include non-restricted signing keys on the same TPM (TPM2_Sign with a non-restricted scheme does NOT prependTPM_GENERATED_VALUE), vTPM-as-a-service back-ends, previously-leaked AKs reused for ad-hoc Sign, and cross-tenant signing endpoints.Compare to the legacy package, which gets this right:
legacy/tpm2/structures.go:667-679(DecodeAttestationData) explicitly rejectsad.Magic != 0xff544347.Fix
Wire the
checktag intounmarshalStructField. After the field's bytes are read, if the field type implementsCheck() error, call it; return the error.Test plan
go build ./tpm2/clean.tpm2/check_tag_test.goadds a regression matrix forUnmarshal[TPMSAttest]that rejectsMagic=0x00000000,0xdeadbeef, and the byte-swapped lookalike0x47ff5443, and that still accepts the legitimate0xff544347. The tests don't import the TPM simulator dep.main(commitab627a9) before the fix: a hand-built 74-byte TPMSAttest withMagic=0,Type=TPM_ST_ATTEST_QUOTE, attacker-chosen 32-byte PCRDigest, attacker ECDSA signature over the blob verifies and the verifier readsparsed.Attested.Quote().PCRDigestas the attacker-chosen value. After this PR,Unmarshal[TPMSAttest]returnscheck tag failed for field 'Magic' of struct 'TPMSAttest': TPM_GENERATED value should be 0xff544347, was 0x0and the verifier short-circuits.