diff --git a/efi/preinstall/check_pcr4.go b/efi/preinstall/check_pcr4.go
new file mode 100644
index 00000000..95c15827
--- /dev/null
+++ b/efi/preinstall/check_pcr4.go
@@ -0,0 +1,451 @@
+// -*- 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 (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+
+ efi "github.com/canonical/go-efilib"
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ secboot_efi "github.com/snapcore/secboot/efi"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+)
+
+var (
+ efiComputePeImageDigest = efi.ComputePeImageDigest
+)
+
+// readLoadOptionFromLog reads the corresponding Boot#### load option from the log,
+// which reflects the value of it at boot time, as opposed to reading it from an
+// EFI variable which may have been modified since booting.
+func readLoadOptionFromLog(log *tcglog.Log, n uint16) (*efi.LoadOption, error) {
+ events := log.Events
+ for len(events) > 0 {
+ ev := events[0]
+ events = events[1:]
+
+ if ev.PCRIndex != internal_efi.PlatformConfigPCR {
+ continue
+ }
+
+ if ev.EventType != tcglog.EventTypeEFIVariableBoot && ev.EventType != tcglog.EventTypeEFIVariableBoot2 {
+ // not a boot variable
+ continue
+ }
+
+ data, ok := ev.Data.(*tcglog.EFIVariableData)
+ if !ok {
+ // decode error data is guaranteed to implement the error interface
+ return nil, fmt.Errorf("boot variable measurement has wrong data format: %w", ev.Data.(error))
+ }
+ if data.VariableName != efi.GlobalVariable {
+ // not a global variable
+ continue
+ }
+ if !strings.HasPrefix(data.UnicodeName, "Boot") || len(data.UnicodeName) != 8 {
+ // name has unexpected prefix or length
+ continue
+ }
+
+ var x uint16
+ if y, err := fmt.Sscanf(data.UnicodeName, "Boot%x", &x); err != nil || y != 1 {
+ continue
+ }
+ if x != n {
+ // wrong load option
+ continue
+ }
+
+ // We've found the correct load option. Decode it from the data stored in the log.
+ opt, err := efi.ReadLoadOption(bytes.NewReader(data.VariableData))
+ if err != nil {
+ return nil, fmt.Errorf("cannot read load option from event data: %w", err)
+ }
+ return opt, nil
+ }
+
+ return nil, errors.New("cannot find specified boot option")
+}
+
+// isLaunchedFromLoadOption returns true if the supplied EV_EFI_BOOT_SERVICES_APPLICATION event
+// is associated with the supplied load option. This will panic if the event is of the
+// wrong type or the event data decodes incorrectly. This works by doing a device path match,
+// which can either be a full match, or a recognized short-form match. This also handles the case
+// where the boot option points to a removable device and the executable associated with the load
+// event is loaded from that device.
+func isLaunchedFromLoadOption(ev *tcglog.Event, opt *efi.LoadOption) (yes bool, err error) {
+ if ev.EventType != tcglog.EventTypeEFIBootServicesApplication {
+ // The caller should check this.
+ panic("unexpected event type")
+ }
+
+ // Grab the device path from the event. For the launch of the initial boot loader, this
+ // will always be a full path.
+ eventDevicePath := ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath
+ if len(eventDevicePath) == 0 {
+ return false, errors.New("EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path")
+ }
+
+ // Try to match the load option.
+ if opt.Attributes&efi.LoadOptionActive == 0 {
+ // the load option isn't active.
+ return false, errors.New("boot option is not active")
+ }
+
+ // Test to see if the load option path matches the load event path in some way. Note
+ // that the load option might be in short-form, but this function takes that into
+ // account.
+ if eventDevicePath.Matches(opt.FilePath) != efi.DevicePathNoMatch {
+ // We have a match. This is very likely to be a launch of the
+ // load option.
+ return true, nil
+ }
+
+ // There's no match with the load option. This might happen when booting from
+ // removable media where the load option specifies the device path pointing to
+ // the bus that the removable media is connected to, but the load event contains
+ // the full path to the initial boot loader, using some extra components.
+ // Unless the load option is already using a short-form path, try appending the
+ // extra components for the removable media from the load event to the load option
+ // path and try testing for a match again.
+ if opt.FilePath.ShortFormType().IsShortForm() {
+ // The load option path is in short-form. We aren't going to find a match.
+ return false, nil
+ }
+
+ // Copy the load option path
+ optFilePath := append(efi.DevicePath{}, opt.FilePath...)
+ if cdrom := efi.DevicePathFindFirstOccurrence[*efi.CDROMDevicePathNode](eventDevicePath); len(cdrom) > 0 {
+ // Booting from CD-ROM.
+ optFilePath = append(optFilePath, cdrom...)
+ } else if hd := efi.DevicePathFindFirstOccurrence[*efi.HardDriveDevicePathNode](eventDevicePath); len(hd) > 0 {
+ // Booting from any removable device with a GPT, such as a USB drive.
+ optFilePath = append(optFilePath, hd...)
+ }
+
+ // With the CDROM() or HD() components of the event file path appended to the
+ // load option path, test for a match again. In this case, we expect a full
+ // match as neither paths are in short-form.
+ return eventDevicePath.Matches(optFilePath) == efi.DevicePathFullMatch, nil
+}
+
+type bootManagerCodeResultFlags int
+
+const (
+ bootManagerCodeSysprepAppsPresent bootManagerCodeResultFlags = 1 << iota
+ bootManagerCodeAbsoluteComputraceRunning
+ bootManagerCodeNotAllLaunchDigestsVerified
+)
+
+// checkBootManagerCodeMeasurements performs some checks on the boot manager code PCR (4).
+//
+// The supplied context is used to attach an EFI variable backend to, for functions that read
+// from EFI variables. The supplied env and log arguments provide other inputs to this function.
+// The pcrAlg argument is the PCR bank that is chosen as the best one to use. The loadImages
+// argument provides a way to supply the load images associated with the current boot, in the
+// order in which they are loaded. The caller must supply at least the IBL (initial boot loader,
+// loaded by the firmware), and the SBL (secondary boot loader, loaded by the IBL), if there is an
+// event in the log for it. These images are used to verify the digests of the
+// EV_EFI_BOOT_SERVICES_APPLICATION events. Other images are optional, but if not all
+// EV_EFI_BOOT_SERVICES_APPLICATION events can be verified, this will set the
+// bootManagerCodeNotAllLaunchDigestsVerified flag.
+//
+// This function ensures that the pre-OS environment is well formed. Either it contains a single
+// EV_OMIT_BOOT_DEVICE_EVENT event or an optional EV_EFI_ACTION "Calling EFI Application from Boot
+// Option" event if the EV_OMIT_BOOT_DEVICE_EVENT event is not present. If the EV_EFI_ACTION event
+// is present, then the next expected event is the EV_SEPARATOR to signal the transition to OS-present.
+// The function considers any EV_EFI_BOOT_SERVICES_APPLICATION events before this to be system
+// preparation applications, and it will set the bootManagerCodeSysprepAppsPresent flag if any are
+// detected. If the BootOptionSupport EFI variable indicates that sysprep apps are not supported but
+// they are present, then an error is returned.
+//
+// The function expects the next event after the EV_SEPARATOR to be a EV_EFI_BOOT_SERVICES_APPLICATION
+// event, either the one associated with the IBL (initial boot loader), or a component of Absolute. If
+// it is Absolute, then this sets the bootManagerCodeAbsoluteComputraceRunning flag, and it then expects
+// the next event to be the one associated with the IBL (based on the value of the BootCurrent EFI variable,
+// and the corresponding EFI_LOAD_OPTION in the TCG log). If the event data is inconsistent with the
+// EFI_LOAD_OPTION for BootCurrent, it returns an error. It verifies that the digest of the event matches
+// the Authenticode digest of the first supplied image, and returns an error if it isn't.
+//
+// Once the IBL image digest is verified, then the digests of all other EV_EFI_BOOT_SERVICES_APPLICATION
+// events in the log are checked, if enough images associated with the current boot are supplied via the
+// loadImages argument. It isn't possible to determine whether these events are generated by the firmware
+// via a call to LoadImage, or whether they are generated by an OS component using the EFI_TCG2_PROTOCOL.
+// In any case, if any OS component loads the next component itself and measures a digest directly without
+// using the LoadImage API, it depends on the presence of the EFI_TCG2_PROTOCOL interface with support for
+// the PE_COFF_IMAGE flag. There's no direct way to test for this, so for this reason, this function requires
+// that the EV_EFI_BOOT_SERVICES_APPLICATION digest associated with the SBL (secondary boot-loader), if
+// there is one, matches the Authenticode digest of the second image supplied via the loadImages argument,
+// and this must be supplied. It's not necessary to supply additional load images, although if there are any
+// more EV_EFI_BOOT_SERVICES_APPLICATION events without a corresponding boot image to test it against, the
+// function sets the bootManagerCodeNotAllLaunchDigestsVerified flag.
+func checkBootManagerCodeMeasurements(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, loadImages []secboot_efi.Image) (result bootManagerCodeResultFlags, err error) {
+ if len(loadImages) == 0 {
+ return 0, errors.New("at least the initial EFI application loaded during this boot must be supplied")
+ }
+ varCtx := env.VarContext(ctx)
+
+ // Obtain the boot option support
+ opts, err := efi.ReadBootOptionSupportVariable(varCtx)
+ if err != nil {
+ return 0, fmt.Errorf("cannot obtain boot option support: %w", err)
+ }
+
+ // Obtain the BootCurrent variable and use this to obtain the corresponding load entry
+ // that was measured to the log. BootXXXX variables are measured to the TPM and so we don't
+ // need to read back from an EFI variable that could have been modified between boot time
+ // and now.
+ current, err := efi.ReadBootCurrentVariable(varCtx)
+ if err != nil {
+ return 0, fmt.Errorf("cannot read BootCurrent variable: %w", err)
+ }
+ bootOpt, err := readLoadOptionFromLog(log, current)
+ if err != nil {
+ return 0, fmt.Errorf("cannot read current Boot%04x load option from log: %w", current, err)
+ }
+
+ var (
+ sysprepSupported = opts&efi.BootOptionSupportSysPrep > 0 // The firmware supports SysPrep applications
+ omitBootDeviceEventsSeen = false // a EV_OMIT_BOOT_DEVICE_EVENTS event has been seen
+ expectingTransitionToOSPresent = false // The next events in PCR4 are expected to be the transition to OS-present
+ seenOSComponentLaunches = 0 // The number of EV_EFI_BOOT_SERVICES_APPLICATION events associated with OS component launches we've seen
+ )
+
+ phaseTracker := newTcgLogPhaseTracker()
+NextEvent:
+ for _, ev := range log.Events {
+ phase, err := phaseTracker.processEvent(ev)
+ if err != nil {
+ return 0, err
+ }
+
+ switch phase {
+ case tcglogPhasePreOSBeforeMeasureSecureBootConfig, tcglogPhasePreOSAfterMeasureSecureBootConfig, tcglogPhasePreOSAfterMeasureSecureBootConfigUnterminated:
+ if ev.PCRIndex != internal_efi.BootManagerCodePCR {
+ // Not PCR4
+ continue NextEvent
+ }
+
+ // Make sure the event data is valid
+ if err, isErr := ev.Data.(error); isErr {
+ return 0, fmt.Errorf("invalid %v event data: %w", ev.EventType, err)
+ }
+
+ if expectingTransitionToOSPresent {
+ // The next events in PCR4 should have taken us to OS-present
+ return 0, fmt.Errorf("unexpected event type %v: expecting transition from pre-OS to OS-present event", ev.EventType)
+ }
+
+ switch ev.EventType {
+ case tcglog.EventTypeOmitBootDeviceEvents:
+ // The digest is the tagged hash of the event data, but we don't bother verifying
+ // that because we just copy this event into the profile if it's present.
+ if omitBootDeviceEventsSeen {
+ return 0, errors.New("already seen a EV_OMIT_BOOT_DEVICE_EVENTS event")
+ }
+ omitBootDeviceEventsSeen = true
+ case tcglog.EventTypeEFIAction:
+ // ok, although 1.05 of the TCG PFP spec is a bit ambiguous here, section 8.2.4 says
+ // the event associated with the first boot attempt, if it is measured, occurs before
+ // the separator (as part of pre-OS). The actual PCR usage section 3.3.4.5 in this version
+ // of the spec and older contradicts this and mentions a bunch of EV_ACTION events that
+ // pertain to BIOS boot. On every device we've tested, this event occurs before the
+ // separator and there are no BIOS boot related EV_ACTION events. 1.06 of the TCG PFP
+ // spec tries to clean this up a bit, removing reference to the EV_ACTION events and
+ // correcting the "Method for measurement" subsection of section 3.3.4.5 to match
+ // section 8.2.4. We reject any EV_ACTION events in PCR4 here anyway.
+ //
+ // EV_EFI_ACTION event digests are the tagged hash of the event data, but we don't bother
+ // verifying this because we just copy the events into the profile.
+ if ev.Data == tcglog.EFICallingEFIApplicationEvent {
+ // This is the signal from BDS that we're about to hand over to the OS.
+ if phase == tcglogPhasePreOSBeforeMeasureSecureBootConfig {
+ return 0, fmt.Errorf("unexpected %v event %q (before secure boot config was measured)", ev.EventType, ev.Data)
+ }
+ if omitBootDeviceEventsSeen {
+ return 0, fmt.Errorf("unexpected %v event %q (because of earlier EV_OMIT_BOOT_DEVICE_EVENTS event)", ev.EventType, ev.Data)
+ }
+
+ // The next event we're expecting is the pre-OS to OS-present transition.
+ //
+ // TODO(chrisccoulson): The TCG PFP spec 1.06 r49 expects there to be a
+ // EV_EFI_ACTION event immediately following this one with the string
+ // "Booting to Option". Whilst the current profile generation code
+ // will preserve what's currently in the log, there needs to be an API for boot
+ // configuration code to specificy the actual boot option to ensure that we
+ // predict the correct value. We currently fail support for PCR4 if this
+ // unsupported EV_EFI_ACTION event is present next.
+ expectingTransitionToOSPresent = true
+ } else {
+ // We're not expecting any other EV_EFI_ACTION event types, although see
+ // the TODO above.
+ return 0, fmt.Errorf("unexpected %s event %q", ev.EventType, ev.Data)
+ }
+ case tcglog.EventTypeEFIBootServicesApplication:
+ // Assume all pre-OS application launches are SysPrep applications. There shouldn't
+ // really be anything else here and there isn't really a reliable way to detect.
+ // It might be possible to match the device path with the next variable in SysPrepOrder,
+ // but these can be modified at runtime to not reflect what they were at boot time,
+ // and SysPrep variables are not measured to the TCG log.
+ //
+ // As we don't do any prediction of sysprep applications (yet - never say never), we
+ // don't verify that the measured Authenticode digest matches the binary at the end of the
+ // device path, if it's reachable from the OS. Although this also suffers from a similar
+ // variation of the issue described above - that path could have been updated between
+ // booting and now.
+ if phase == tcglogPhasePreOSBeforeMeasureSecureBootConfig {
+ // Application launches before the secure boot configuration has been measured is a bug.
+ return 0, fmt.Errorf("encountered pre-OS %v event for %v before secure boot configuration has been measured", ev.EventType, ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath)
+ }
+ if !sysprepSupported {
+ // The firmware indicated that sysprep applications aren't supported yet it still
+ // loaded one!
+ return 0, fmt.Errorf("encountered pre-OS %v event for %v when SysPrep applications are not supported", ev.EventType, ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath)
+
+ }
+ result |= bootManagerCodeSysprepAppsPresent // Record that this boot contains system preparation applications.
+ default:
+ // We're not expecting any other event types during the pre-OS phase.
+ return 0, fmt.Errorf("unexpected pre-OS event type %v", ev.EventType)
+ }
+ case tcglogPhaseOSPresent:
+ if ev.PCRIndex != internal_efi.BootManagerCodePCR {
+ // Not PCR4
+ continue NextEvent
+ }
+
+ if ev.EventType != tcglog.EventTypeEFIBootServicesApplication {
+ // Only care about EV_EFI_BOOT_SERVICES_APPLICATION events for checking
+ if seenOSComponentLaunches == 0 {
+ // The only events we're expecting in OS-present for now is EV_EFI_BOOT_SERVICES_APPLICATION.
+ return 0, fmt.Errorf("unexpected OS-present log event type %v (expected EV_EFI_BOOT_SERVICES_APPLICATION)", ev.EventType)
+ }
+ // Once the IBL has launched, other event types are acceptable as long as the policy generation
+ // code associated with the component in the secboot efi package emits them.
+ continue NextEvent
+ }
+
+ data, eventDataOk := ev.Data.(*tcglog.EFIImageLoadEvent)
+
+ switch seenOSComponentLaunches {
+ case 0:
+ if !eventDataOk {
+ // Only require the event data to be ok for firmware generated events. This is because
+ // OS components might create invalid data (and shim actually does), so we ignore those
+ // errors.
+ return 0, fmt.Errorf("invalid OS-present EV_EFI_BOOT_SERVICES_APPLICATION event data: %w", ev.Data.(error))
+ }
+ // Check if this launch is associated with the EFI_LOAD_OPTION associated with
+ // the current boot.
+ isBootOptLaunch, err := isLaunchedFromLoadOption(ev, bootOpt)
+ if err != nil {
+ return 0, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is associated with the current boot load option: %w", data.DevicePath, err)
+ }
+ if isBootOptLaunch {
+ // We have the EV_EFI_BOOT_SERVICES_APPLICATION event associated with the IBL launch.
+ seenOSComponentLaunches += 1
+ } else {
+ // We have an EV_EFI_BOOT_SERVICES_APPLICATION that didn't come from the load option
+ // associated with the current boot.
+ // Test to see if it's part of Absolute. If it is, that's fine - we copy this into
+ // the profile, so we don't need to do any other verification of it and we don't have
+ // anything to verify the Authenticode digest against anyway. We have a device path,
+ // but not one that we're able to read back from.
+ //
+ // If this isn't Absolute, we bail with an error. We don't support anything else being
+ // loaded here, and ideally Absolute will be turned off as well.
+ if result&bootManagerCodeAbsoluteComputraceRunning > 0 {
+ return 0, fmt.Errorf("OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is not associated with the current boot load option and is not Absolute", data.DevicePath)
+ }
+
+ isAbsolute, err := internal_efi.IsAbsoluteAgentLaunch(ev)
+ if err != nil {
+ return 0, fmt.Errorf("cannot determine if OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is associated with Absolute: %w", data.DevicePath, err)
+ }
+ if !isAbsolute {
+ return 0, fmt.Errorf("OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for %v is not associated with the current boot load option and is not Absolute", data.DevicePath)
+ }
+ result |= bootManagerCodeAbsoluteComputraceRunning
+ continue NextEvent // We want to start a new iteration, else we'll consume one of the loadImages below.
+ }
+ default:
+ seenOSComponentLaunches += 1
+ }
+
+ if len(loadImages) == 0 {
+ result |= bootManagerCodeNotAllLaunchDigestsVerified
+ if seenOSComponentLaunches < 3 {
+ // This launch is associated with a SBL - we know this because we check that
+ // len(loadImages) > 0 at the start of the function, so we will never reach
+ // this condition for the IBL.
+ return 0, errors.New("cannot verify digest for EV_EFI_BOOT_SERVICES_APPLICATION event associated with the secondary boot loader")
+ }
+ continue NextEvent
+ }
+
+ image := loadImages[0]
+ loadImages = loadImages[1:]
+
+ err := func() error {
+ r, err := image.Open()
+ if err != nil {
+ return fmt.Errorf("cannot open image %s: %w", image, err)
+ }
+ defer r.Close()
+
+ digest, err := efiComputePeImageDigest(pcrAlg.GetHash(), r, r.Size())
+ if err != nil {
+ return fmt.Errorf("cannot compute Authenticode digest of OS-present application %s: %w", image, err)
+ }
+ if bytes.Equal(digest, ev.Digests[pcrAlg]) {
+ // The PE digest of the application matches what's in the log, so we're all good.
+ return nil
+ }
+
+ // Digest in log does not match PE image digest. Compute flat-file digest and compare against that
+ // for diagnostic purposes.
+ r2 := io.NewSectionReader(r, 0, r.Size())
+ h := pcrAlg.NewHash()
+ if _, err := io.Copy(h, r2); err != nil {
+ return fmt.Errorf("cannot compute flat file digest of OS-present application %s: %w", image, err)
+ }
+ if !bytes.Equal(h.Sum(nil), ev.Digests[pcrAlg]) {
+ // Still no digest match
+ return fmt.Errorf("log contains unexpected EV_EFI_BOOT_SERVICES_APPLICATION digest for OS-present application %s (calculated PE digest: %#x, log value: %#x) - were the correct boot images supplied?",
+ image, digest, ev.Digests[pcrAlg])
+ }
+ // We have a digest match, so something loaded this component outside of the LoadImage API and used the
+ // legacy EFI_TCG_PROTOCOL API to measure it, or used the proper EFI_TCG2_PROTOCOL API without the
+ // PE_COFF_IMAGE flag. In any case, WithBootManagerCodeProfile() will mis-predict the loading of this.
+ return fmt.Errorf("log contains unexpected EV_EFI_BOOT_SERVICES_APPLICATION digest for OS-present application %s: log digest matches flat file digest (%#x) which suggests an image loaded outside of the LoadImage API and firmware lacking support for the EFI_TCG2_PROTOCOL and/or the PE_COFF_IMAGE flag", image, h.Sum(nil))
+ }()
+ if err != nil {
+ return 0, err
+ }
+ }
+ }
+ return result, nil
+}
diff --git a/efi/preinstall/check_pcr4_test.go b/efi/preinstall/check_pcr4_test.go
new file mode 100644
index 00000000..9d954da4
--- /dev/null
+++ b/efi/preinstall/check_pcr4_test.go
@@ -0,0 +1,853 @@
+// -*- 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 (
+ "context"
+ "crypto"
+ "errors"
+ "io"
+
+ efi "github.com/canonical/go-efilib"
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ secboot_efi "github.com/snapcore/secboot/efi"
+ . "github.com/snapcore/secboot/efi/preinstall"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+ "github.com/snapcore/secboot/internal/efitest"
+ "github.com/snapcore/secboot/internal/testutil"
+ . "gopkg.in/check.v1"
+)
+
+type pcr4Suite struct{}
+
+var _ = Suite(&pcr4Suite{})
+
+func (s *pcr4Suite) TestReadLoadOptionFromLog(c *C) {
+ log := efitest.NewLog(c, &efitest.LogOptions{})
+ opt, err := ReadLoadOptionFromLog(log, 3)
+ c.Assert(err, IsNil)
+ c.Check(opt, DeepEquals, &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ OptionalData: []byte{},
+ })
+}
+
+func (s *pcr4Suite) TestReadLoadOptionFromLogNotExist(c *C) {
+ log := efitest.NewLog(c, &efitest.LogOptions{})
+ _, err := ReadLoadOptionFromLog(log, 10)
+ c.Check(err, ErrorMatches, `cannot find specified boot option`)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGood(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.NVMENamespaceDevicePathNode{
+ NamespaceID: 0x1,
+ NamespaceUUID: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.NVMENamespaceDevicePathNode{
+ NamespaceID: 0x1,
+ NamespaceUUID: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsTrue)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodShortFormOpt(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.NVMENamespaceDevicePathNode{
+ NamespaceID: 0x1,
+ NamespaceUUID: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsTrue)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodRemovableCDROM(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1},
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1},
+ &efi.CDROMDevicePathNode{
+ BootEntry: 0,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000},
+ efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsTrue)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGoodRemovableUSB(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x8},
+ &efi.USBDevicePathNode{
+ ParentPortNumber: 2,
+ InterfaceNumber: 0},
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x8},
+ &efi.USBDevicePathNode{
+ ParentPortNumber: 2,
+ InterfaceNumber: 0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsTrue)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNoMatch(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x1e482b5b, 0x6600, 0x427f, 0xb394, [...]uint8{0x9a, 0x68, 0x82, 0x3e, 0x55, 0x04})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1},
+ &efi.CDROMDevicePathNode{
+ BootEntry: 0,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000},
+ efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsFalse)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNoMatchRemovable(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x8},
+ &efi.USBDevicePathNode{
+ ParentPortNumber: 2,
+ InterfaceNumber: 0},
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI"),
+ },
+ },
+ }
+
+ yes, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, IsNil)
+ c.Check(yes, testutil.IsFalse)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionEmptyDevicePath(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 1,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{},
+ },
+ }
+
+ _, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path`)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNotActive(c *C) {
+ opt := &efi.LoadOption{
+ Attributes: 0,
+ Description: "ubuntu",
+ FilePath: efi.DevicePath{
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ }
+ ev := &tcglog.Event{
+ PCRIndex: internal_efi.BootManagerCodePCR,
+ EventType: tcglog.EventTypeEFIBootServicesApplication,
+ Data: &tcglog.EFIImageLoadEvent{
+ LocationInMemory: 0x6556c018,
+ LengthInMemory: 955072,
+ DevicePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.NVMENamespaceDevicePathNode{
+ NamespaceID: 0x1,
+ NamespaceUUID: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"),
+ },
+ },
+ }
+
+ _, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, ErrorMatches, `boot option is not active`)
+}
+
+type mockImageReader struct {
+ contents []byte
+ digest tpm2.Digest
+ closed bool
+}
+
+func (r *mockImageReader) ReadAt(data []byte, offset int64) (int, error) {
+ copy(data, r.contents[offset:])
+ return len(data), nil
+}
+
+func (r *mockImageReader) Close() error {
+ if r.closed {
+ return errors.New("already closed")
+ }
+ r.closed = true
+ return nil
+}
+
+func (r *mockImageReader) Size() int64 {
+ return int64(len(r.contents))
+}
+
+type mockImage struct {
+ contents []byte // Used to produce a flat-file digest
+ digest tpm2.Digest // Authenticode digest
+}
+
+func (i *mockImage) String() string {
+ return "mock image"
+}
+
+func (i *mockImage) Open() (secboot_efi.ImageReader, error) {
+ return &mockImageReader{
+ contents: i.contents,
+ digest: i.digest}, nil
+}
+
+type testCheckBootManagerCodeMeasurementsParams struct {
+ env internal_efi.HostEnvironment
+ pcrAlg tpm2.HashAlgorithmId
+ images []secboot_efi.Image
+ expectedResult BootManagerCodeResultFlags
+}
+
+func (s *pcr4Suite) testCheckBootManagerCodeMeasurements(c *C, params *testCheckBootManagerCodeMeasurementsParams) error {
+ log, err := params.env.ReadEventLog()
+ c.Assert(err, IsNil)
+
+ i := 0
+ restore := MockEfiComputePeImageDigest(func(alg crypto.Hash, r io.ReaderAt, sz int64) ([]byte, error) {
+ c.Check(alg, Equals, params.pcrAlg.GetHash())
+ c.Assert(r, testutil.ConvertibleTo, &mockImageReader{})
+ imageReader := r.(*mockImageReader)
+ c.Assert(params.images[i], testutil.ConvertibleTo, &mockImage{})
+ c.Check(sz, Equals, int64(len(params.images[i].(*mockImage).contents)))
+ i += 1
+ return imageReader.digest, nil
+ })
+ defer restore()
+
+ result, err := CheckBootManagerCodeMeasurements(context.Background(), params.env, log, params.pcrAlg, params.images)
+ if err != nil {
+ return err
+ }
+ c.Check(result, Equals, params.expectedResult)
+ return nil
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodSHA256(c *C) {
+ // Test good result with SHA-256
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")},
+ &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")},
+ },
+ expectedResult: 0,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodSHA384(c *C) {
+ // Test good result with SHA-384, when log also contains SHA-256
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA384,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "030ac3c913dab858f1d69239115545035cff671d6229f95577bb0ffbd827b35abaf6af6bfd223e04ecc9b60a9803642d")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "6c2df9007211786438be210b6908f2935d0b25ebdcd2c65621826fd2ec55fb9fbacbfe080d48db98f0ef970273b8254a")},
+ &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "42f61b3089f5ce0646b422a59c9632065db2630f3e5b01690e63c41420ed31f10ff2a191f3440f9501109fc85f7fb00f")},
+ },
+ expectedResult: 0,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithSysprepApp(c *C) {
+ // Test good result with sysprep application in log before OS-present
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ IncludeSysPrepAppLaunch: true,
+ })),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")},
+ &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")},
+ },
+ expectedResult: BootManagerCodeSysprepAppsPresent,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithAbsolute(c *C) {
+ // Test good result with Absolute running as part of OS-present before shim
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d}),
+ })),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")},
+ &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")},
+ },
+ expectedResult: BootManagerCodeAbsoluteComputraceRunning,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithMissingImages(c *C) {
+ // Test good result with missing boot images - the function needs at least the IBL (in our case, shim) and SBL (in our case, grub)
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ })),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")},
+ },
+ expectedResult: BootManagerCodeNotAllLaunchDigestsVerified,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithoutCallingEFIApplicationEvent(c *C) {
+ // Test good result with log without EV_EFI_ACTION "Calling EFI Application from Boot Option" event
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ NoCallingEFIApplicationEvent: true,
+ })),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")},
+ &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")},
+ },
+ expectedResult: 0,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadNoImages(c *C) {
+ // Test error result because no load images were supplied
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ })
+ c.Check(err, ErrorMatches, `at least the initial EFI application loaded during this boot must be supplied`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadBootOptionSupport(c *C) {
+ // Test error result because of invalid BootOptionSupport
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `cannot obtain boot option support: variable contents has an unexpected size \(5 bytes\)`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadBootCurrent(c *C) {
+ // Test error result because of bad BootCurrent
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `cannot read BootCurrent variable: BootCurrent variable contents has the wrong size \(1 bytes\)`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadLoadOption(c *C) {
+ // Test error result because BootCurrent value doesn't match entry in log
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x5, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `cannot read current Boot0005 load option from log: cannot find specified boot option`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadMissingInitialLaunch(c *C) {
+ // Test error result because IBL launch can't be identified - it doesn't match boot entry in log that BootCurrent points to
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `OS-present EV_EFI_BOOT_SERVICES_APPLICATION event for \\PciRoot\(0x0\)\\Pci\(0x1d,0x0\)\\Pci\(0x0,0x0\)\\NVMe\(0x1,00-00-00-00-00-00-00-00\)\\HD\(1,GPT,66de947b-fdb2-4525-b752-30d66bb2b960\)\\\\EFI\\ubuntu\\shimx64\.efi is not associated with the current boot load option and is not Absolute`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadMissingSBL(c *C) {
+ // Test error result because it wasn't possible to verify Authenticode digest for SBL launch (in our case, grub), as it wasn't supplied
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `cannot verify digest for EV_EFI_BOOT_SERVICES_APPLICATION event associated with the secondary boot loader`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadSBLMeasuredFlatFileDigest_NoEFITCG2Protocol(c *C) {
+ // Test error result because digest associated with SBL launch matches the file digest rather than the Authenticode digest, which in the
+ // case of shim -> grub, might mean that EFI_TCG2_PROTOCOL is missing from the firmware.
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ // We have to cheat a bit here because the digest is hardcoded in the test log. We set an invalid Authenticode digest for the mock image so the intial test
+ // fails and then have the following code digest the same string that produces the log digest ("mock grub executable"), to get a digest that matches what's in
+ // the log so the test thinks that the log contains the flat file digest.
+ &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd")},
+ },
+ })
+ c.Check(err, ErrorMatches, `log contains unexpected EV_EFI_BOOT_SERVICES_APPLICATION digest for OS-present application mock image: log digest matches flat file digest \(0xd5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0\) which suggests an image loaded outside of the LoadImage API and firmware lacking support for the EFI_TCG2_PROTOCOL and/or the PE_COFF_IMAGE flag`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadProvidedBootImages(c *C) {
+ // Test error result because the SBL image (in our case grub) has a digest that doesn't match what's in the log
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ // In this case, we provide an invalid Authenticode digest, and a payload that also subsequently produces the wrong file digest.
+ &mockImage{contents: []byte("foo"), digest: testutil.DecodeHexString(c, "80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd")},
+ },
+ })
+ c.Check(err, ErrorMatches, `log contains unexpected EV_EFI_BOOT_SERVICES_APPLICATION digest for OS-present application mock image \(calculated PE digest: 0x80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd, log value: 0xd5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0\) - were the correct boot images supplied\?`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadWithUnsupportedSysprepApp(c *C) {
+ // Test error result because a sysprep app was detected when BootOptionSupport indicates they aren't supported
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x03, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ IncludeSysPrepAppLaunch: true,
+ })),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `encountered pre-OS EV_EFI_BOOT_SERVICES_APPLICATION event for \\PciRoot\(0x0\)\\Pci\(0x1d,0x0\)\\Pci\(0x0,0x0\)\\NVMe\(0x1,00-00-00-00-00-00-00-00\)\\HD\(1,GPT,66de947b-fdb2-4525-b752-30d66bb2b960\)\\\\EFI\\Dell\\sysprep.efi when SysPrep applications are not supported`)
+}
+
+func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadUnexpectedTransitionToOSPresentEvent(c *C) {
+ // Test error result because of an unexpected event after the EV_EFI_ACTION "Calling EFI
+ // Application from Boot Option" event
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ var eventsCopy []*tcglog.Event
+ for _, ev := range log.Events {
+ eventsCopy = append(eventsCopy, ev)
+ if ev.PCRIndex == internal_efi.BootManagerCodePCR && ev.EventType == tcglog.EventTypeEFIAction {
+ // Just duplicate the EV_EFI_ACTION "Calling EFI Application from Boot Option" event
+ eventsCopy = append(eventsCopy, ev)
+ }
+ }
+ log.Events = eventsCopy
+
+ err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{
+ {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}},
+ {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}},
+ }),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ images: []secboot_efi.Image{
+ &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")},
+ },
+ })
+ c.Check(err, ErrorMatches, `unexpected event type EV_EFI_ACTION: expecting transition from pre-OS to OS-present event`)
+}
+
+// TODO (other error cases - harder):
+// - Unexpected event types in log in pre-OS environment.
+// - More than one EV_OMIT_BOOT_DEVICE_EVENTS event in log in pre-OS environment.
+// - EV_EFI_ACTION "Calling EFI Application from Boot Option" along with EV_OMIT_BOOT_DEVICE_EVENTS event.
+// - Event other than EV_EFI_BOOT_SERVICES_APPLICATION in initial OS-present environment (ie, after EV_SEPARATOR).
+// - Image open errors.
+// - efi.ComputePeImageDigest errors.
+// - EV_EFI_BOOT_SERVICES_APPLICATION event after detecting Absolute, and which is not associated with the IBL launch (doesn't match boot option)
+// - internal_efi.IsAbsoluteAgentLaunch error.
diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go
index 985ba7e8..4f1863b5 100644
--- a/efi/preinstall/export_test.go
+++ b/efi/preinstall/export_test.go
@@ -19,7 +19,13 @@
package preinstall
+import (
+ "crypto"
+ "io"
+)
+
type (
+ BootManagerCodeResultFlags = bootManagerCodeResultFlags
CheckDriversAndAppsMeasurementsResult = checkDriversAndAppsMeasurementsResult
CheckTPM2DeviceFlags = checkTPM2DeviceFlags
CpuVendor = cpuVendor
@@ -28,23 +34,27 @@ type (
)
const (
- CheckTPM2DeviceInVM = checkTPM2DeviceInVM
- CheckTPM2DevicePostInstall = checkTPM2DevicePostInstall
- CpuVendorIntel = cpuVendorIntel
- CpuVendorAMD = cpuVendorAMD
- DetectVirtNone = detectVirtNone
- DetectVirtVM = detectVirtVM
- DriversAndAppsPresent = driversAndAppsPresent
- MeFamilyUnknown = meFamilyUnknown
- MeFamilySps = meFamilySps
- MeFamilyTxe = meFamilyTxe
- MeFamilyMe = meFamilyMe
- MeFamilyCsme = meFamilyCsme
- NoDriversAndAppsPresent = noDriversAndAppsPresent
+ BootManagerCodeSysprepAppsPresent = bootManagerCodeSysprepAppsPresent
+ BootManagerCodeAbsoluteComputraceRunning = bootManagerCodeAbsoluteComputraceRunning
+ BootManagerCodeNotAllLaunchDigestsVerified = bootManagerCodeNotAllLaunchDigestsVerified
+ CheckTPM2DeviceInVM = checkTPM2DeviceInVM
+ CheckTPM2DevicePostInstall = checkTPM2DevicePostInstall
+ CpuVendorIntel = cpuVendorIntel
+ CpuVendorAMD = cpuVendorAMD
+ DetectVirtNone = detectVirtNone
+ DetectVirtVM = detectVirtVM
+ DriversAndAppsPresent = driversAndAppsPresent
+ MeFamilyUnknown = meFamilyUnknown
+ MeFamilySps = meFamilySps
+ MeFamilyTxe = meFamilyTxe
+ MeFamilyMe = meFamilyMe
+ MeFamilyCsme = meFamilyCsme
+ NoDriversAndAppsPresent = noDriversAndAppsPresent
)
var (
CalculateIntelMEFamily = calculateIntelMEFamily
+ CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements
CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR
CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements
CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank
@@ -54,7 +64,17 @@ var (
CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings
DetectVirtualization = detectVirtualization
DetermineCPUVendor = determineCPUVendor
+ IsLaunchedFromLoadOption = isLaunchedFromLoadOption
OpenAndCheckTPM2Device = openAndCheckTPM2Device
ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs
ReadIntelMEVersionFromMEISysfs = readIntelMEVersionFromMEISysfs
+ ReadLoadOptionFromLog = readLoadOptionFromLog
)
+
+func MockEfiComputePeImageDigest(fn func(crypto.Hash, io.ReaderAt, int64) ([]byte, error)) (restore func()) {
+ orig := efiComputePeImageDigest
+ efiComputePeImageDigest = fn
+ return func() {
+ efiComputePeImageDigest = orig
+ }
+}
diff --git a/go.mod b/go.mod
index 75900441..720f623c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,11 @@ go 1.18
require (
github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb
- github.com/canonical/go-efilib v1.2.0
+ github.com/canonical/go-efilib v1.3.1
github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3
github.com/canonical/go-tpm2 v1.7.6
- github.com/canonical/tcglog-parser v0.0.0-20240820013904-60cf7cbc7c5d
+ github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981
github.com/snapcore/snapd v0.0.0-20220714152900-4a1f4c93fc85
golang.org/x/crypto v0.21.0
golang.org/x/sys v0.19.0
diff --git a/go.sum b/go.sum
index fef8f7ed..1ee5d1bc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb h1:+kA/9oHTqUx4P08ywKvmd7a1wOL3RLTrE0K958C15x8=
github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb/go.mod h1:6j8Sw3dwYVcBXltEeGklDoK/8UJVJNQPUkg1ZdQUgbk=
github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4/go.mod h1:9Sr9kd7IhQPYqaU5nut8Ky97/CtlhHDzQncQnrULgDM=
-github.com/canonical/go-efilib v1.2.0 h1:+fvJdkj3oVyURFtfk8gSft6pdKyVzzdzNn9GC1kMJw8=
-github.com/canonical/go-efilib v1.2.0/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ=
+github.com/canonical/go-efilib v1.3.1 h1:KnVlqrKn0ZDGAbgQt9tke5cvtqNRCmpEp0v7RGUVpqs=
+github.com/canonical/go-efilib v1.3.1/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ=
github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s=
github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 h1:ZE2XMRFHcwlib3uU9is37+pKkkMloVoEPWmgQ6GK1yo=
github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s=
@@ -12,8 +12,8 @@ github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61/go.mod h1:vG41hd
github.com/canonical/go-tpm2 v1.7.6 h1:9k9OAEEp9xKp4h2WJwfTUNivblJi4L5Wjx7Q/LkSTSQ=
github.com/canonical/go-tpm2 v1.7.6/go.mod h1:Dz0PQRmoYrmk/4BLILjRA+SFzuqEo1etAvYeAJiMhYU=
github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8=
-github.com/canonical/tcglog-parser v0.0.0-20240820013904-60cf7cbc7c5d h1:v3gTMnOF/eT79eZnUSbHR18IJqHAXUog5SwiPn+HRXk=
-github.com/canonical/tcglog-parser v0.0.0-20240820013904-60cf7cbc7c5d/go.mod h1:ywdPBqUGkuuiitPpVWCfilf2/gq+frhq4CNiNs9KyHU=
+github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981 h1:vrUzSfbhl8mzdXPzjxq4jXZPCCNLv18jy6S7aVTS2tI=
+github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981/go.mod h1:ywdPBqUGkuuiitPpVWCfilf2/gq+frhq4CNiNs9KyHU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
diff --git a/internal/efitest/log.go b/internal/efitest/log.go
index d10fd387..e3ddac41 100644
--- a/internal/efitest/log.go
+++ b/internal/efitest/log.go
@@ -398,9 +398,10 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log {
// Mock boot config measurements
{
- var order [4]uint8
+ var order [6]uint8
binary.LittleEndian.PutUint16(order[0:], 3)
binary.LittleEndian.PutUint16(order[2:], 1)
+ binary.LittleEndian.PutUint16(order[4:], 0)
builder.hashLogExtendEvent(c, bytesHashData(order[:]), &logEvent{
pcrIndex: 1,
eventType: tcglog.EventTypeEFIVariableBoot,
@@ -454,6 +455,37 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log {
UnicodeName: "Boot0001",
VariableData: optionBytes}})
}
+ {
+ option := &efi.LoadOption{
+ Attributes: 1,
+ Description: "External USB",
+ FilePath: efi.DevicePath{
+ &efi.ACPIDevicePathNode{
+ HID: 0x0a0341d0,
+ UID: 0x0},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x1d},
+ &efi.PCIDevicePathNode{
+ Function: 0x0,
+ Device: 0x0},
+ &efi.HardDriveDevicePathNode{
+ PartitionNumber: 1,
+ PartitionStart: 0x800,
+ PartitionSize: 0x100000,
+ Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x423f43ec, 0xd34e, 0x4b55, 0xb2d7, [...]uint8{0x42, 0x2b, 0xa5, 0x02, 0x1c, 0xc4})),
+ MBRType: efi.GPT},
+ efi.FilePathDevicePathNode("\\EFI\\BOOT\\BOOTX64.EFI")}}
+ optionBytes, err := option.Bytes()
+ c.Assert(err, IsNil)
+ builder.hashLogExtendEvent(c, option, &logEvent{
+ pcrIndex: 1,
+ eventType: tcglog.EventTypeEFIVariableBoot,
+ data: &tcglog.EFIVariableData{
+ VariableName: efi.GlobalVariable,
+ UnicodeName: "Boot0000",
+ VariableData: optionBytes}})
+ }
// Mock boundary between pre-OS and OS-present
if !opts.NoCallingEFIApplicationEvent {