diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index df961071..92e72584 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -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() } diff --git a/efi/preinstall/check_pcr0.go b/efi/preinstall/check_pcr0.go new file mode 100644 index 00000000..34986368 --- /dev/null +++ b/efi/preinstall/check_pcr0.go @@ -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 . + * + */ + +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 +} diff --git a/efi/preinstall/check_pcr0_test.go b/efi/preinstall/check_pcr0_test.go new file mode 100644 index 00000000..c9ad430b --- /dev/null +++ b/efi/preinstall/check_pcr0_test.go @@ -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 . + * + */ + +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`) +} diff --git a/efi/preinstall/check_pcr2.go b/efi/preinstall/check_pcr2.go new file mode 100644 index 00000000..894ac231 --- /dev/null +++ b/efi/preinstall/check_pcr2.go @@ -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 . + * + */ + +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 +} diff --git a/efi/preinstall/check_pcr2_test.go b/efi/preinstall/check_pcr2_test.go new file mode 100644 index 00000000..ad5da9bd --- /dev/null +++ b/efi/preinstall/check_pcr2_test.go @@ -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 . + * + */ + +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`) +} diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index f0607077..01b4d509 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -23,6 +23,7 @@ type ( CheckTPM2DeviceFlags = checkTPM2DeviceFlags CpuVendor = cpuVendor DetectVirtResult = detectVirtResult + DriversAndAppsResultFlags = driversAndAppsResultFlags MeVersion = meVersion PlatformFirmwareProtectionsResultFlags = platformFirmwareProtectionsResultFlags ) @@ -34,6 +35,7 @@ const ( CpuVendorAMD = cpuVendorAMD DetectVirtNone = detectVirtNone DetectVirtVM = detectVirtVM + DriversAndAppsDriversPresent = driversAndAppsDriversPresent MeFamilyUnknown = meFamilyUnknown MeFamilySps = meFamilySps MeFamilyTxe = meFamilyTxe @@ -45,8 +47,10 @@ const ( var ( CalculateIntelMEFamily = calculateIntelMEFamily CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR + CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank CheckForKernelIOMMU = checkForKernelIOMMU + CheckPlatformFirmwareMeasurements = checkPlatformFirmwareMeasurements CheckPlatformFirmwareProtections = checkPlatformFirmwareProtections CheckPlatformFirmwareProtectionsIntelMEI = checkPlatformFirmwareProtectionsIntelMEI CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings