WIP processing image load events (#216)

This commit is contained in:
Tom D 2021-05-10 12:11:58 -07:00 committed by GitHub
parent 9b857465d0
commit ee5bb94c43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 307 additions and 84 deletions

View File

@ -79,6 +79,23 @@ const (
EFIVariableAuthority EventType = 0x800000e0
)
// EFIDeviceType describes the type of a device specified by a device path.
type EFIDeviceType uint8
// "Device Path Protocol" type values.
//
// Section 9.3.2 of the UEFI specification, accessible at:
// https://uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
const (
HardwareDevice EFIDeviceType = 0x01
ACPIDevice EFIDeviceType = 0x02
MessagingDevice EFIDeviceType = 0x03
MediaDevice EFIDeviceType = 0x04
BBSDevice EFIDeviceType = 0x05
EndDeviceArrayMarker EFIDeviceType = 0x7f
)
// ErrSigMissingGUID is returned if an EFI_SIGNATURE_DATA structure was parsed
// successfully, however was missing the SignatureOwner GUID. This case is
// handled specially as a workaround for a bug relating to authority events.
@ -416,3 +433,87 @@ func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
}
return certificates, err
}
type EFIDevicePathElement struct {
Type EFIDeviceType
Subtype uint8
Data []byte
}
// EFIImageLoad describes an EFI_IMAGE_LOAD_EVENT structure.
type EFIImageLoad struct {
Header EFIImageLoadHeader
DevPathData []byte
}
type EFIImageLoadHeader struct {
LoadAddr uint64
Length uint64
LinkAddr uint64
DevicePathLen uint64
}
func parseDevicePathElement(r io.Reader) (EFIDevicePathElement, error) {
var (
out EFIDevicePathElement
dataLen uint16
)
if err := binary.Read(r, binary.LittleEndian, &out.Type); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading type: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &out.Subtype); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading subtype: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &dataLen); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data len: %v", err)
}
if dataLen > maxNameLen {
return EFIDevicePathElement{}, fmt.Errorf("device path data too long: %d > %d", dataLen, maxNameLen)
}
if dataLen < 4 {
return EFIDevicePathElement{}, fmt.Errorf("device path data too short: %d < %d", dataLen, 4)
}
out.Data = make([]byte, dataLen-4)
if err := binary.Read(r, binary.LittleEndian, &out.Data); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data: %v", err)
}
return out, nil
}
func (h *EFIImageLoad) DevicePath() ([]EFIDevicePathElement, error) {
var (
r = bytes.NewReader(h.DevPathData)
out []EFIDevicePathElement
)
for r.Len() > 0 {
e, err := parseDevicePathElement(r)
if err != nil {
return nil, err
}
if e.Type == EndDeviceArrayMarker {
return out, nil
}
out = append(out, e)
}
return out, nil
}
// ParseEFIImageLoad parses an EFI_IMAGE_LOAD_EVENT structure.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf#page=17
func ParseEFIImageLoad(r io.Reader) (ret EFIImageLoad, err error) {
err = binary.Read(r, binary.LittleEndian, &ret.Header)
if err != nil {
return
}
if ret.Header.DevicePathLen > maxNameLen {
return EFIImageLoad{}, fmt.Errorf("device path structure too long: %d > %d", ret.Header.DevicePathLen, maxNameLen)
}
ret.DevPathData = make([]byte, ret.Header.DevicePathLen)
err = binary.Read(r, binary.LittleEndian, &ret.DevPathData)
return
}

View File

@ -56,8 +56,20 @@ type SecurebootState struct {
// PostSeparatorAuthority describes the use of a secure-boot key to authorize
// the execution of a binary after the separator.
PostSeparatorAuthority []x509.Certificate
// DriverLoadSourceHints describes the origin of boot services drivers.
// This data is not tamper-proof and must only be used as a hint.
DriverLoadSourceHints []DriverLoadSource
}
// DriverLoadSource describes the logical origin of a boot services driver.
type DriverLoadSource uint8
const (
UnknownSource DriverLoadSource = iota
PciMmioSource
)
// ParseSecurebootState parses a series of events to determine the
// configuration of secure boot on a device. An error is returned if
// the state cannot be determined, or if the event log is structured
@ -78,14 +90,16 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
// - No UEFI debugger was attached.
var (
out SecurebootState
seenSeparator bool
seenAuthority bool
seenVars = map[string]bool{}
out SecurebootState
seenSeparator7 bool
seenSeparator2 bool
seenAuthority bool
seenVars = map[string]bool{}
driverSources [][]internal.EFIDevicePathElement
)
for _, e := range events {
if e.Index != 7 {
if e.Index != 7 && e.Index != 2 {
continue
}
@ -93,99 +107,156 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
if err != nil {
return nil, fmt.Errorf("unrecognised event type: %v", err)
}
digestVerify := e.digestEquals(e.Data)
switch et {
case internal.Separator:
if seenSeparator {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIAction:
if string(e.Data) == "UEFI Debug Mode" {
return nil, errors.New("a UEFI debugger was present during boot")
}
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
switch e.Index {
case 7:
switch et {
case internal.Separator:
if seenSeparator7 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator7 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIVariableDriverConfig:
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
}
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
}
seenVars[v.VarName()] = true
if seenSeparator {
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
}
case internal.EFIAction:
if string(e.Data) == "UEFI Debug Mode" {
return nil, errors.New("a UEFI debugger was present during boot")
}
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
}
case internal.EFIVariableDriverConfig:
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
}
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
}
seenVars[v.VarName()] = true
if seenSeparator7 {
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
}
switch v.VarName() {
case "SecureBoot":
if len(v.VariableData) != 1 {
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
}
out.Enabled = v.VariableData[0] == 1
case "PK":
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
}
case "KEK":
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
}
case "db":
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
}
case "dbx":
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
}
}
case internal.EFIVariableAuthority:
a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data))
if err != nil {
// Workaround for: https://github.com/google/go-attestation/issues/157
if err == internal.ErrSigMissingGUID {
// Versions of shim which do not carry
// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
// have an erroneous additional byte in the event, which breaks digest
// verification. If verification failed, we try removing the last byte.
if digestVerify != nil && len(e.Data) > 0 {
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
switch v.VarName() {
case "SecureBoot":
if len(v.VariableData) != 1 {
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
}
out.Enabled = v.VariableData[0] == 1
case "PK":
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
}
case "KEK":
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
}
case "db":
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
}
case "dbx":
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
}
} else {
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
}
}
seenAuthority = true
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
}
if !seenSeparator {
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
} else {
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
case internal.EFIVariableAuthority:
a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data))
if err != nil {
// Workaround for: https://github.com/google/go-attestation/issues/157
if err == internal.ErrSigMissingGUID {
// Versions of shim which do not carry
// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
// have an erroneous additional byte in the event, which breaks digest
// verification. If verification failed, we try removing the last byte.
if digestVerify != nil && len(e.Data) > 0 {
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
}
} else {
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
}
}
seenAuthority = true
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
}
if !seenSeparator7 {
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
} else {
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
}
default:
return nil, fmt.Errorf("unexpected event type in PCR7: %v", et)
}
default:
return nil, fmt.Errorf("unexpected event type: %v", et)
case 2:
switch et {
case internal.Separator:
if seenSeparator2 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator2 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIBootServicesDriver:
if !seenSeparator2 {
imgLoad, err := internal.ParseEFIImageLoad(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.sequence, err)
}
dp, err := imgLoad.DevicePath()
if err != nil {
return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.sequence, err)
}
driverSources = append(driverSources, dp)
}
}
}
}
// Compute driver source hints based on the EFI device path observed in
// EFI Boot-services driver-load events.
sourceLoop:
for _, source := range driverSources {
// We consider a driver to have originated from PCI-MMIO if any number
// of elements in the device path [1] were PCI devices, and are followed by
// an element representing a "relative offset range" read.
// In the wild, we have typically observed 4-tuple device paths for such
// devices: ACPI device -> PCI device -> PCI device -> relative offset.
//
// [1]: See section 9 of the UEFI specification v2.6 or greater.
var seenPCI bool
for _, e := range source {
// subtype 0x1 corresponds to a PCI device (See: 9.3.2.1)
if e.Type == internal.HardwareDevice && e.Subtype == 0x1 {
seenPCI = true
}
// subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8)
if seenPCI && e.Type == internal.MediaDevice && e.Subtype == 0x8 {
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource)
continue sourceLoop
}
}
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource)
}
if !out.Enabled {
return &out, nil
}

View File

@ -15,6 +15,7 @@
package attest
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"testing"
@ -124,3 +125,53 @@ func TestSecureBootBug157(t *testing.T) {
t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want)
}
}
func b64MustDecode(input string) []byte {
b, err := base64.StdEncoding.DecodeString(input)
if err != nil {
panic(err)
}
return b
}
func TestSecureBootOptionRom(t *testing.T) {
raw, err := ioutil.ReadFile("testdata/option_rom_eventlog")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
elr, err := ParseEventLog(raw)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
pcrs := []PCR{
{'\x00', b64MustDecode("AVGK7ch6DvUF0nJh74NYCefaAIY="), '\x03'},
{'\x01', b64MustDecode("vr/0wIpmd0c6tgTO3vuC+FDN6IM="), '\x03'},
{'\x02', b64MustDecode("NmoxoMB1No8OEIVzM+ou1uigD9M="), '\x03'},
{'\x03', b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), '\x03'},
{'\x04', b64MustDecode("OfOIw5WekEaUcm9MAVttzq4GgKE="), '\x03'},
{'\x05', b64MustDecode("cjoFIM9/KXhUh0K9FUFwayRGRZ4="), '\x03'},
{'\x06', b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), '\x03'},
{'\x07', b64MustDecode("IN59+6a838ytrX4+sJnJHU2Xxa0="), '\x03'},
}
events, err := elr.Verify(pcrs)
if err != nil {
t.Errorf("failed to verify log: %v", err)
}
sbs, err := ParseSecurebootState(events)
if err != nil {
t.Errorf("failed parsing secureboot state: %v", err)
}
if got, want := len(sbs.PostSeparatorAuthority), 2; got != want {
t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want)
}
if got, want := len(sbs.DriverLoadSourceHints), 1; got != want {
t.Fatalf("len(sbs.DriverLoadSourceHints) = %d, want %d", got, want)
}
if got, want := sbs.DriverLoadSourceHints[0], PciMmioSource; got != want {
t.Errorf("sbs.DriverLoadSourceHints[0] = %v, want %v", got, want)
}
}

BIN
attest/testdata/option_rom_eventlog vendored Normal file

Binary file not shown.