Skip to content

Commit ea70dc1

Browse files
committed
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.
1 parent f877374 commit ea70dc1

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

attest/internal/events.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,27 @@ 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+
vendorHeader := make([]byte, signatures.Header.SignatureHeaderSize)
396+
if err := binary.Read(buf, binary.LittleEndian, &vendorHeader); err != nil {
397+
return nil, nil, fmt.Errorf("reading signature vendor header: %w", err)
398+
}
399+
}
384400

385401
signatureType := signatures.Header.SignatureType
386402
switch signatureType {
387403
case certX509SigGUID: // X509 certificate
388-
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
404+
for sigOffset := int(signatures.Header.SignatureHeaderSize); uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
389405
signature := efiSignatureData{}
390406
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-efiGUIDSize)
391407
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
@@ -404,7 +420,7 @@ func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
404420
certificates = append(certificates, *cert)
405421
}
406422
case hashSHA256SigGUID: // SHA256
407-
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
423+
for sigOffset := int(signatures.Header.SignatureHeaderSize); uint32(sigOffset) < signatures.Header.SignatureListSize-efiSignatureListHeaderSize; {
408424
signature := efiSignatureData{}
409425
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-efiGUIDSize)
410426
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)

attest/internal/events_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,60 @@ func TestParseUEFIVariableData(t *testing.T) {
6565
t.Errorf("ParseUEFIVariableData() mismatch (-want +got):\n%s", diff)
6666
}
6767
}
68+
69+
func TestParseEfiSignatureListNonZeroSignatureHeaderSize(t *testing.T) {
70+
// Regression test: parseEfiSignatureList() must skip SignatureHeaderSize
71+
// vendor bytes before reading signature entries (UEFI spec section 31.4.1).
72+
//
73+
// Without the fix, a crafted EFI_SIGNATURE_LIST with SignatureHeaderSize=48
74+
// causes attacker-controlled vendor header bytes to be returned as trusted
75+
// SHA256 hashes, allowing arbitrary hash injection into the trusted list.
76+
77+
// hashSHA256SigGUID bytes (little-endian)
78+
sigType := [16]byte{
79+
0x26, 0x16, 0xc4, 0xc1, 0x4c, 0x50, 0x92, 0x40,
80+
0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28,
81+
}
82+
83+
const (
84+
sigSize = 48 // 16-byte GUID + 32-byte SHA256
85+
sigHeaderSize = 48 // vendor header exactly one entry worth
86+
)
87+
88+
// Vendor header: zero GUID + attacker-chosen hash (0xAA * 32)
89+
attackerHash := bytes.Repeat([]byte{0xAA}, 32)
90+
vendorHeader := make([]byte, sigHeaderSize)
91+
copy(vendorHeader[16:], attackerHash)
92+
93+
// One legitimate entry: zero GUID + 0xBB * 32
94+
legitHash := bytes.Repeat([]byte{0xBB}, 32)
95+
legitEntry := make([]byte, sigSize)
96+
copy(legitEntry[16:], legitHash)
97+
98+
sigListSize := uint32(28 + sigHeaderSize + len(legitEntry)) // 124 bytes
99+
100+
writeU32 := func(buf *bytes.Buffer, v uint32) {
101+
b := []byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24)}
102+
buf.Write(b)
103+
}
104+
var buf bytes.Buffer
105+
buf.Write(sigType[:])
106+
writeU32(&buf, sigListSize)
107+
writeU32(&buf, uint32(sigHeaderSize))
108+
writeU32(&buf, uint32(sigSize))
109+
buf.Write(vendorHeader)
110+
buf.Write(legitEntry)
111+
112+
_, hashes, err := parseEfiSignatureList(buf.Bytes())
113+
if err != nil {
114+
// Acceptable: the bound check rejects the oversized SignatureHeaderSize.
115+
return
116+
}
117+
118+
// Attacker hash must NOT appear in the trusted list.
119+
for _, h := range hashes {
120+
if bytes.Equal(h, attackerHash) {
121+
t.Error("attacker-controlled vendor header bytes returned as trusted SHA256 hash")
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)