Skip to content

Commit

Permalink
preinstall: Add simple checks for PCRs 0 and 2
Browse files Browse the repository at this point in the history
This adds some trivial to checks to both PCRs 0 and 2:
- that they contain events of the expected type.
- that there are no measurements to them from the OS-present phase.

As we don't do any sort of prediction for these PCRs (yet), it doesn't
perform additional checking such as ensuring that the event data is
consistent with the event digests when the event digests are a tagged
hash of the event data (ie, not purely informational).

Note that this modifies the internal/efitest package to emit
EV_S_CRTM_VERSION and EV_EFI_PLATFORM_FIRMWARE_BLOB events with the
correct event data types.
  • Loading branch information
chrisccoulson committed Aug 14, 2024
1 parent 40aac1c commit bb5db95
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 3 deletions.
3 changes: 0 additions & 3 deletions efi/fw_load_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ func (s *fwLoadHandlerSuite) testMeasureImageStart(c *C, data *testFwMeasureImag
handler := NewFwLoadHandler(efitest.NewLog(c, data.logOptions))
c.Check(handler.MeasureImageStart(ctx), IsNil)
c.Check(ctx.events, DeepEquals, data.expectedEvents)
for _, event := range ctx.events {
c.Logf("pcr:%d, type:%v, digest:%#x", event.pcr, event.eventType, event.digest)
}
c.Check(collector.More(), testutil.IsFalse)
return ctx.FwContext()
}
Expand Down
110 changes: 110 additions & 0 deletions efi/preinstall/check_pcr0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package preinstall

import (
"errors"
"fmt"

"github.com/canonical/tcglog-parser"
internal_efi "github.com/snapcore/secboot/internal/efi"
)

// checkPlatformFirmwareMeasurements checks measurements related to platfor firmware in PCR0,
// including that measurements are of expected types, and that no measurements are made during
// the OS-present phase.
func checkPlatformFirmwareMeasurements(log *tcglog.Log) error {
// Iterate over the log until OS-present and make sure that we have expected
// event types
events := log.Events
for len(events) > 0 {
ev := events[0]
events = events[1:]

if ev.PCRIndex != internal_efi.PlatformFirmwarePCR {
// Not PCR0
continue
}
if ev.EventType == tcglog.EventTypeSeparator {
break
}

switch ev.EventType {
case tcglog.EventTypePostCode, tcglog.EventTypeEFIPlatformFirmwareBlob:
// Platform firmware blobs - deprecated.
// EV_POST_CODE should contain a non-NULL terminated string or a
// UEFI_PLATFORM_FIRMWARE_BLOB structure. EV_EFI_PLATFORM_FIRMWARE_BLOB
// should contain a UEFI_PLATFORM_FIRMWARE_BLOB structure.
case tcglog.EventTypeNoAction:
// Information, not measured
case tcglog.EventTypeSCRTMContents, tcglog.EventTypeSCRTMVersion:
// SCRTM measurements. EV_S_SCRTM_CONTENTS should either contain a
// UEFI_PLATFORM_FIRMWARE_BLOB or UEFI_PLATFORM_FIRMWARE_BLOB2 structure,
// or it can contain a NULL terminated ASCII string if measured by a H-CRTM
// event. EV_S_CRTM_VERSION should contain a NULL terminated UCS2 string or
// a EFI_GUID.
//
// We don't make any special accommodations for these when measured as part
// of a H-CRTM sequence, so it's possible we already mis-predicted PCR0 and
// marked it invalid before getting to this point.
//
// EV_S_CRTM_VERSION is not informational but we don't check that the data matches
// the event digests because we don't do any prediction for this value.
case tcglog.EventTypeNonhostCode, tcglog.EventTypeNonhostInfo:
// Non-host platform code running on an embedded controller. The second one is used
// if the host platform cannot reliably measure the non-host code. The event data is
// determined by the platform manufacturer.
case tcglog.EventTypePostCode2, tcglog.EventTypeEFIPlatformFirmwareBlob2:
// Platform firmware blobs.
// EV_POST_CODE2 should contain a non-NULL terminated string or a
// UEFI_PLATFORM_FIRMWARE_BLOB2 structure. EV_EFI_PLATFORM_FIRMWARE_BLOB
// should contain a EF_EFI_PLATFORM_FIRMWARE_BLOB2 structure.
case tcglog.EventTypeEFIBootServicesDriver, tcglog.EventTypeEFIRuntimeServicesDriver:
// Platform firmware blobs as PE images and loaded via the LoadImage API.
// We don't check the digests here because it's likely that the device path
// takes us to something we can't read, and we don't do any prediction here
// yet either.
case tcglog.EventTypeEFIHCRTMEvent:
// a H-CRTM sequence that occurred before TPM2_Startup. There may be more than
// one of these.
// There should be a corresponding EV_NO_ACTION event indicating that the startup
// locality is 4, and there may be other EV_NO_ACTION events containing
// TCG_HCRTMComponentEvent structures.
case tcglog.EventTypeEFISPDMFirmwareBlob:
// Firmware of a component that supports SPDM "GET_MEASUREMENTS".
// Note that this is very new (only in the TCG PFP spec v1.06)
default:
return fmt.Errorf("unexpected pre-OS log event type %v", ev.EventType)
}
}

// Nothing should measure to PCR0 outside of pre-OS - we'll generate an invalid profile
// if it does.
for len(events) > 0 {
ev := events[0]
events = events[1:]

if ev.PCRIndex == internal_efi.PlatformFirmwarePCR {
return errors.New("firmware measures events as part of the OS-present environment")
}
}

return nil
}
67 changes: 67 additions & 0 deletions efi/preinstall/check_pcr0_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package preinstall_test

import (
"github.com/canonical/tcglog-parser"
. "github.com/snapcore/secboot/efi/preinstall"
internal_efi "github.com/snapcore/secboot/internal/efi"
"github.com/snapcore/secboot/internal/efitest"
. "gopkg.in/check.v1"
)

type pcr0Suite struct{}

var _ = Suite(&pcr0Suite{})

func (s *pcr0Suite) TestCheckPlatformFirmwareMeasurementsGood(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{})
c.Check(CheckPlatformFirmwareMeasurements(log), IsNil)
}

func (s *pcr0Suite) TestCheckPlatformFirmwareMeasurementsGoodSL3(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{StartupLocality: 3})
c.Check(CheckPlatformFirmwareMeasurements(log), IsNil)
}

func (s *pcr0Suite) TestCheckPlatformFirmwareMeasurementsGoodHCRTM(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{StartupLocality: 4})
c.Check(CheckPlatformFirmwareMeasurements(log), IsNil)
}

func (s *pcr0Suite) TestCheckPlatformFirmwareMeasurementsUnexpectedEventType(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{})
for i, ev := range log.Events {
if ev.PCRIndex == internal_efi.PlatformFirmwarePCR && ev.EventType == tcglog.EventTypeEFIPlatformFirmwareBlob {
log.Events[i].EventType = tcglog.EventTypeNonhostConfig
}
}
c.Check(CheckPlatformFirmwareMeasurements(log), ErrorMatches, `unexpected pre-OS log event type EV_NONHOST_CONFIG`)
}

func (s *pcr0Suite) TestCheckPlatformFirmwareMeasurementsUnexpectedOSPresentEvent(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{})
log.Events = append(log.Events, &tcglog.Event{
PCRIndex: internal_efi.PlatformFirmwarePCR,
EventType: tcglog.EventTypeEFIAction,
Data: tcglog.StringEventData("foo"),
})
c.Check(CheckPlatformFirmwareMeasurements(log), ErrorMatches, `firmware measures events as part of the OS-present environment`)
}
97 changes: 97 additions & 0 deletions efi/preinstall/check_pcr2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package preinstall

import (
"errors"
"fmt"

"github.com/canonical/tcglog-parser"
internal_efi "github.com/snapcore/secboot/internal/efi"
)

type driversAndAppsResultFlags int

const (
driversAndAppsDriversPresent driversAndAppsResultFlags = 1 << iota
)

func checkDriversAndAppsMeasurements(log *tcglog.Log) (result driversAndAppsResultFlags, err error) {
// Iterate over the log until OS-present and make sure we have expected
// event types.
events := log.Events
for len(events) > 0 {
ev := events[0]
events = events[1:]

if ev.PCRIndex != internal_efi.DriversAndAppsPCR {
// Not PCR2
continue
}
if ev.EventType == tcglog.EventTypeSeparator {
break
}

switch ev.EventType {
case tcglog.EventTypeAction, tcglog.EventTypeEFIAction:
// Some sort of action. The event data is a non-NULL terminated ASCII string.
// The data in these events is not informational (the event digests are the tagged
// hashes of the event data), but we don't verify that the event data is consistent
// with the digests yet because we don't do any prediction here.
case tcglog.EventTypeNonhostCode, tcglog.EventTypeNonhostInfo:
// Non-host platform code running on an embedded controller. The second one is used
// if the host platform cannot reliably measure the non-host code. The event data is
// determined by the platform manufacturer and is purely informational.
case tcglog.EventTypeEFIBootServicesApplication, tcglog.EventTypeEFIBootServicesDriver, tcglog.EventTypeEFIRuntimeServicesDriver:
// Code from value-added-retailer component loaded via the LoadImage API.
// We don't check the digests here because it's likely that the device path
// takes us to something we can't read, and we don't do any prediction here
// yet either.
result |= driversAndAppsDriversPresent
case tcglog.EventTypeEFIPlatformFirmwareBlob:
// Code blob from value-added-retailer component - deprecated. Event data should
// contain a UEFI_PLATFORM_FIRMWARE_BLOB structure.
result |= driversAndAppsDriversPresent
case tcglog.EventTypeEFIPlatformFirmwareBlob2:
// Code blob from value-added-retailer component. Event data should contain a
// UEFI_PLATFORM_FIRMWARE_BLOB2 structure.
result |= driversAndAppsDriversPresent
case tcglog.EventTypeEFISPDMFirmwareBlob:
// Firmware of a component that supports SPDM "GET_MEASUREMENTS".
// Note that this is very new (only in the TCG PFP spec v1.06)
result |= driversAndAppsDriversPresent
default:
return 0, fmt.Errorf("unexpected pre-OS log event type %v", ev.EventType)
}
}

// Nothing should measure to PCR2 outside of pre-OS - we'll generate an invalid profile
// if it does.
for len(events) > 0 {
ev := events[0]
events = events[1:]

if ev.PCRIndex == internal_efi.DriversAndAppsPCR {
return 0, errors.New("firmware measures events as part of the OS-present environment")
}
}

return result, nil
}
68 changes: 68 additions & 0 deletions efi/preinstall/check_pcr2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package preinstall_test

import (
"github.com/canonical/tcglog-parser"
. "github.com/snapcore/secboot/efi/preinstall"
internal_efi "github.com/snapcore/secboot/internal/efi"
"github.com/snapcore/secboot/internal/efitest"
. "gopkg.in/check.v1"
)

type pcr2Suite struct{}

var _ = Suite(&pcr2Suite{})

func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsGood(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{})
result, err := CheckDriversAndAppsMeasurements(log)
c.Check(err, IsNil)
c.Check(result, Equals, DriversAndAppsResultFlags(0))
}

func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsWithDrivers(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{IncludeDriverLaunch: true})
result, err := CheckDriversAndAppsMeasurements(log)
c.Check(err, IsNil)
c.Check(result, Equals, DriversAndAppsDriversPresent)
}

func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsUnexpectedEventType(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{IncludeDriverLaunch: true})
for i, ev := range log.Events {
if ev.PCRIndex == internal_efi.DriversAndAppsPCR && ev.EventType == tcglog.EventTypeEFIBootServicesDriver {
log.Events[i].EventType = tcglog.EventTypeNonhostConfig
}
}
_, err := CheckDriversAndAppsMeasurements(log)
c.Check(err, ErrorMatches, `unexpected pre-OS log event type EV_NONHOST_CONFIG`)
}

func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsUnexpectedOSPresentEvent(c *C) {
log := efitest.NewLog(c, &efitest.LogOptions{})
log.Events = append(log.Events, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: tcglog.EventTypeEFIAction,
Data: tcglog.StringEventData("foo"),
})
_, err := CheckDriversAndAppsMeasurements(log)
c.Check(err, ErrorMatches, `firmware measures events as part of the OS-present environment`)
}
4 changes: 4 additions & 0 deletions efi/preinstall/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type (
CheckTPM2DeviceFlags = checkTPM2DeviceFlags
CpuVendor = cpuVendor
DetectVirtResult = detectVirtResult
DriversAndAppsResultFlags = driversAndAppsResultFlags
MeVersion = meVersion
PlatformFirmwareProtectionsResultFlags = platformFirmwareProtectionsResultFlags
)
Expand All @@ -34,6 +35,7 @@ const (
CpuVendorAMD = cpuVendorAMD
DetectVirtNone = detectVirtNone
DetectVirtVM = detectVirtVM
DriversAndAppsDriversPresent = driversAndAppsDriversPresent
MeFamilyUnknown = meFamilyUnknown
MeFamilySps = meFamilySps
MeFamilyTxe = meFamilyTxe
Expand All @@ -45,8 +47,10 @@ const (
var (
CalculateIntelMEFamily = calculateIntelMEFamily
CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR
CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements
CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank
CheckForKernelIOMMU = checkForKernelIOMMU
CheckPlatformFirmwareMeasurements = checkPlatformFirmwareMeasurements
CheckPlatformFirmwareProtections = checkPlatformFirmwareProtections
CheckPlatformFirmwareProtectionsIntelMEI = checkPlatformFirmwareProtectionsIntelMEI
CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings
Expand Down

0 comments on commit bb5db95

Please sign in to comment.