Skip to content

tcg: skip SignatureHeader vendor bytes in parseEfiSignatureList (hash injection)#34

Open
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:fix/efi-signature-list-hash-injection
Open

tcg: skip SignatureHeader vendor bytes in parseEfiSignatureList (hash injection)#34
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:fix/efi-signature-list-hash-injection

Conversation

@evilgensec
Copy link
Copy Markdown

What

parseEfiSignatureList (tcg/events.go) iterated EFI_SIGNATURE_DATA entries starting at sigOffset := 0 instead of SignatureHeaderSize, and never skipped the vendor SignatureHeader bytes. Per the UEFI spec (§31.4.1), SignatureHeaderSize bytes of vendor data sit between the fixed list header and the actual signature entries and must be skipped.

As a result, a crafted EFI_SIGNATURE_LIST with zero real entries but attacker-chosen vendor-header bytes has those bytes misread as EFI_SIGNATURE_DATA entries. Through the exported UEFIVariableData.SignatureData() — which extract/secureboot.go calls for PK/KEK/db/dbx while building SecureBootState from an attestation event log — this injects attacker-chosen hashes/certs into the trusted secure-boot signature database. The event log is attacker-controlled attestation evidence (e.g. a confidential-VM guest's own CCEL + RTMR), so a malicious attester can make the verifier accept a forged secure-boot db/dbx and still return success.

Minimal reproduction (a list whose only content is a 16-byte owner GUID + a 32-byte attacker hash, declared entirely as the vendor header):

v := &UEFIVariableData{VariableData: data}
_, hashes, err := v.SignatureData()
// before this fix: err == nil and hashes == [4141...41]
//   — a "trusted" SHA256 db/dbx hash that was never a real signature entry

The missing SignatureSize - 16 / SignatureListSize - 28 lower-bound guards additionally allow uint32 underflow (infinite loop, or a ~4 GiB make([]byte, ...)).

Why

This is the same defect — and the same fix — as GHSA-9r4w-jg96-92mv in google/go-attestation, whose copy of parseEfiSignatureList received these guards in PRs #502 / #486 / #500. This repository forked the pre-patch version of the function and never picked them up. (go-attestation's patched loop is for sigOffset := int(SignatureHeaderSize); ... with a SignatureHeaderSize >= remainingListSize guard and a buf.Seek(SignatureHeaderSize); this fork still had for sigOffset := 0; ... with none. The sibling parser parseDevicePathElement in this same file does have a lower-bound guard, so the omission here is a genuine gap.)

Fix

Port the guards: reject SignatureListSize < 28, SignatureSize < 16, and SignatureSize / SignatureHeaderSize exceeding the remaining list space; Seek past the vendor header; and start both entry loops at SignatureHeaderSize. Adds a regression test asserting the vendor bytes are no longer returned as a trusted hash.

Testing

go test ./tcg/... ./extract/... passes (existing real-event-log tests unaffected), plus the new regression test.

… injection)

parseEfiSignatureList iterated EFI_SIGNATURE_DATA entries starting at offset 0 instead of SignatureHeaderSize, so the vendor-specific SignatureHeader bytes (which per UEFI spec section 31.4.1 sit between the fixed header and the signature entries) were misread as signature entries. A crafted TPM/CCEL event log with zero real entries but attacker-chosen vendor bytes therefore injected arbitrary hashes/certs into the secure-boot db/dbx lists returned by UEFIVariableData.SignatureData(), which extract/secureboot.go uses to build the trusted SecureBootState - i.e. an attacker-controlled event log makes the verifier accept a forged secure-boot signature database.

Same defect and threat model as GHSA-9r4w-jg96-92mv, fixed in google/go-attestation's copy of this function (PRs #502/#486/#500) but never propagated to this fork. Port the guards: reject SignatureListSize < 28, SignatureSize < 16, and SignatureSize/SignatureHeaderSize exceeding the remaining list space (uint32 underflow / ~4 GiB make), seek past the vendor header, and start both entry loops at SignatureHeaderSize. Adds a regression test.
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.

1 participant