Skip to content

Commit b6e905e

Browse files
authored
attest/internal: skip SignatureHeaderSize vendor bytes in parseEfiSignatureList (#502)
* attest/internal: skip SignatureHeaderSize vendor bytes in parseEfiSignatureList Per UEFI specification section 31.4.1, an EFI_SIGNATURE_LIST contains SignatureHeaderSize bytes of vendor-specific data between the fixed 28-byte header and the actual signature entries. parseEfiSignatureList() did not advance the buffer past these vendor bytes before reading entries. For hashSHA256SigGUID lists, this allowed attacker-controlled vendor header bytes to be appended to the trusted SHA256 hash list. A crafted TPM event log could inject arbitrary SHA256 hashes into the verifier's trusted measurement database, enabling a remote attestation verifier to accept a compromised boot state. Fix: - Validate: SignatureHeaderSize must not exceed remaining list space - Skip SignatureHeaderSize vendor bytes via binary.Read before entry loops - Fix both certX509SigGUID and hashSHA256SigGUID loop start offsets - Add regression test confirming vendor bytes are not trusted as hashes Reported via Google OSS VRP issue #513787653. * attest/internal: address review feedback on SignatureHeaderSize fix - Use buf.Seek() instead of binary.Read() to skip vendor header bytes (no need to allocate vendorHeader since we do not use its contents) - Add sha256HashSize const instead of magic literal 32 - Reuse buildEFISignatureListData() in regression test - Use efiSignatureListHeaderSize instead of magic literal 28 * attest/internal: fix gofmt formatting * attest/internal: split SignatureHeaderSize test into two deterministic tests Split TestParseEfiSignatureListNonZeroSignatureHeaderSize into: - TestParseEfiSignatureListOversizedSignatureHeaderSize: verifies the bound check rejects SignatureHeaderSize >= remainingListSize - TestParseEfiSignatureListVendorHeaderNotTrusted: verifies vendor bytes are skipped and do not appear in the trusted hash list * attest/internal: simplify TestParseEfiSignatureListVendorHeaderNotTrusted Check length and first element directly instead of iterating, as suggested by reviewer. --------- Co-authored-by: Prasanna Dabi <prasanna8585@users.noreply.github.com>
1 parent 33eb399 commit b6e905e

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

attest/internal/events.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,26 @@ func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
381381
if signatures.Header.SignatureSize > remainingListSize {
382382
return nil, nil, fmt.Errorf("SignatureSize %d exceeds remaining signature list space %d", signatures.Header.SignatureSize, remainingListSize)
383383
}
384+
// Guard against hash injection via oversized SignatureHeaderSize.
385+
// Per UEFI spec section 31.4.1, SignatureHeaderSize bytes of vendor data
386+
// appear between the fixed header and the actual signature entries.
387+
// SignatureHeaderSize must not consume the entire remaining list space.
388+
if signatures.Header.SignatureHeaderSize >= remainingListSize {
389+
return nil, nil, fmt.Errorf("SignatureHeaderSize %d exceeds remaining signature list space %d", signatures.Header.SignatureHeaderSize, remainingListSize)
390+
}
391+
// Skip the vendor-specific SignatureHeader bytes per UEFI spec section 31.4.1.
392+
// Without this, vendor bytes are misread as signature entries, allowing a
393+
// crafted event log to inject arbitrary hashes into the trusted hash list.
394+
if signatures.Header.SignatureHeaderSize > 0 {
395+
if _, err := buf.Seek(int64(signatures.Header.SignatureHeaderSize), io.SeekCurrent); err != nil {
396+
return nil, nil, fmt.Errorf("seeking past signature vendor header: %w", err)
397+
}
398+
}
384399

385400
signatureType := signatures.Header.SignatureType
386401
switch signatureType {
387402
case certX509SigGUID: // X509 certificate
388-
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
403+
for sigOffset := int(signatures.Header.SignatureHeaderSize); uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
389404
signature := efiSignatureData{}
390405
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-efiGUIDSize)
391406
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
@@ -404,7 +419,7 @@ func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
404419
certificates = append(certificates, *cert)
405420
}
406421
case hashSHA256SigGUID: // SHA256
407-
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
422+
for sigOffset := int(signatures.Header.SignatureHeaderSize); uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
408423
signature := efiSignatureData{}
409424
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-efiGUIDSize)
410425
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)

attest/internal/events_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,55 @@ func TestParseUEFIVariableData(t *testing.T) {
6565
t.Errorf("ParseUEFIVariableData() mismatch (-want +got):\n%s", diff)
6666
}
6767
}
68+
69+
func TestParseEfiSignatureListOversizedSignatureHeaderSize(t *testing.T) {
70+
sigType := [16]byte{
71+
0x26, 0x16, 0xc4, 0xc1, 0x4c, 0x50, 0x92, 0x40,
72+
0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28,
73+
}
74+
const (
75+
sha256HashSize = 32
76+
sigSize = efiGUIDSize + sha256HashSize
77+
)
78+
// sigHeaderSize == remainingListSize: consumes all remaining space.
79+
// The bound check must reject this.
80+
sigHeaderSize := sigSize
81+
sigListSize := uint32(efiSignatureListHeaderSize + sigHeaderSize)
82+
data := buildEFISignatureListData(sigType, sigListSize, uint32(sigHeaderSize), sigSize, 0)
83+
_, _, err := parseEfiSignatureList(data)
84+
if err == nil {
85+
t.Error("parseEfiSignatureList() accepted oversized SignatureHeaderSize, want error")
86+
}
87+
}
88+
89+
func TestParseEfiSignatureListVendorHeaderNotTrusted(t *testing.T) {
90+
sigType := [16]byte{
91+
0x26, 0x16, 0xc4, 0xc1, 0x4c, 0x50, 0x92, 0x40,
92+
0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28,
93+
}
94+
const (
95+
sha256HashSize = 32
96+
sigSize = efiGUIDSize + sha256HashSize
97+
vendorHeaderSize = sha256HashSize // smaller than sigSize so bound check passes
98+
)
99+
// Attacker-controlled vendor header bytes.
100+
attackerBytes := bytes.Repeat([]byte{0xAA}, vendorHeaderSize)
101+
// One legitimate entry.
102+
legitHash := bytes.Repeat([]byte{0xBB}, sha256HashSize)
103+
legitEntry := make([]byte, sigSize)
104+
copy(legitEntry[efiGUIDSize:], legitHash)
105+
sigListSize := uint32(efiSignatureListHeaderSize + vendorHeaderSize + len(legitEntry))
106+
data := buildEFISignatureListData(sigType, sigListSize, vendorHeaderSize, sigSize, 0)
107+
data = append(data, attackerBytes...)
108+
data = append(data, legitEntry...)
109+
_, hashes, err := parseEfiSignatureList(data)
110+
if err != nil {
111+
t.Fatalf("parseEfiSignatureList() returned unexpected error: %v", err)
112+
}
113+
if len(hashes) != 1 {
114+
t.Fatalf("parseEfiSignatureList returned %d hashes, expected 1, hashes: %v", len(hashes), hashes)
115+
}
116+
if !bytes.Equal(hashes[0], legitHash) {
117+
t.Errorf("parseEfiSignatureList returned hash %x, expected %x", hashes[0], legitHash)
118+
}
119+
}

0 commit comments

Comments
 (0)