mirror of
https://github.com/google/go-attestation.git
synced 2025-01-20 03:16:24 +00:00
WIP processing image load events (#216)
This commit is contained in:
parent
9b857465d0
commit
ee5bb94c43
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
BIN
attest/testdata/option_rom_eventlog
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user