diff --git a/efi/preinstall/check_pcr4.go b/efi/preinstall/check_pcr4.go
new file mode 100644
index 00000000..0909f284
--- /dev/null
+++ b/efi/preinstall/check_pcr4.go
@@ -0,0 +1,410 @@
+// -*- 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.
+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.PlatformFirmwareConfigPCR {
+ continue
+ }
+ if ev.EventType == tcglog.EventTypeSeparator {
+ // BootXXXX variables are measured during pre-OS, so we can bail here.
+ break
+ }
+
+ 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")
+}
+
+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")
+ }
+ data, ok := ev.Data.(*tcglog.EFIImageLoadEvent)
+ if !ok {
+ // the data resulting from decode errors is guaranteed to implement error
+ return false, fmt.Errorf("EV_EFI_BOOT_SERVICES_APPLICATION event has wrong data format: %w", ev.Data.(error))
+ }
+ if len(data.DevicePath) == 0 {
+ return false, errors.New("empty device path for image load event")
+ }
+
+ // Try to match the load option.
+ if opt.Attributes&efi.LoadOptionActive == 0 {
+ return false, errors.New("boot option is not active")
+ }
+ if data.DevicePath.Matches(opt.FilePath) != efi.DevicePathNoMatch {
+ // On the balance of probabilities, this is most likely a launch of the
+ // load option, given that we have some sort of path match with it.
+ return true, nil
+ }
+
+ return false, nil
+}
+
+type bootManagerCodeResultFlags int
+
+const (
+ bootManagerCodeSysprepAppsPresent bootManagerCodeResultFlags = 1 << iota
+ bootManagerCodeAbsoluteComputraceRunning
+ bootManagerCodeNotAllLaunchDigestsVerified
+)
+
+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)
+
+ opts, err := efi.ReadBootOptionSupportVariable(varCtx)
+ if err != nil {
+ return 0, fmt.Errorf("cannot obtain boot option support: %w", err)
+ }
+ sysprepSupported := opts&efi.BootOptionSupportSysPrep > 0
+
+ // Iterate over the log until OS-present and make sure we have expected
+ // event types.
+ var expectingSeparator bool
+ events := log.Events
+ for len(events) > 0 {
+ ev := events[0]
+ events = events[1:]
+
+ if ev.PCRIndex != internal_efi.BootManagerCodePCR {
+ // Not PCR4
+ continue
+ }
+ if ev.EventType == tcglog.EventTypeSeparator {
+ // Bail out here at the transition to OS-present.
+ // We replay this from the log so we don't need to verify the consistency between the
+ // event data and digests.
+ break
+ }
+ if expectingSeparator {
+ // We should have bailed out of the pre-OS part of the testing.
+ return 0, fmt.Errorf("unexpected event type %v: expecting transition from pre-OS to OS-present event", ev.EventType)
+ }
+
+ omitBootDeviceEvents := false
+ switch {
+ case ev.EventType == tcglog.EventTypeOmitBootDeviceEvents:
+ // The event data should be "BOOT ATTEMPTS OMITTED" and the event digests should
+ // be the tagged hash of this, but we don't verify that because this event just gets
+ // copied to the profile without any prediction. If we support any prediction of this
+ // event in the future, eg, by toggling firmware settings, then we should definitely
+ // verify this event's data is consistent with its digests.
+ if omitBootDeviceEvents {
+ return 0, fmt.Errorf("already seen a %v event", ev.EventType)
+ }
+ omitBootDeviceEvents = true
+ case ev.EventType == 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, although we don't
+ // test that here because we just copy the events to the profile without any prediction.
+ // If we support any prediction of these specific events in the future, eg, by toggling
+ // firmware settings, then we should definitely verify that the event data is consistent
+ // with the event digests.
+ if ev.Data == tcglog.EFICallingEFIApplicationEvent {
+ // This is the signal from BDS that we're about to hand over to the OS.
+
+ if omitBootDeviceEvents {
+ return 0, fmt.Errorf("unexpected %v event %q (because of earlier %v event)", ev.EventType, ev.Data, tcglog.EventTypeOmitBootDeviceEvents)
+ }
+
+ // 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.
+ expectingSeparator = true
+ } else {
+ // We're not expecting any other EV_EFI_ACTION event types.
+ return 0, fmt.Errorf("unexpected %s event %q", ev.EventType, ev.Data)
+ }
+ case ev.EventType == 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 !sysprepSupported {
+ // The firmware indicated that sysprep applications aren't supported yet it still
+ // loaded one!
+ if err, isErr := ev.Data.(error); isErr {
+ return 0, fmt.Errorf("encountered pre-OS application launch when SysPrep applications are not supported, and unable to decode UEFI_IMAGE_LOAD: %w", err)
+ }
+ return 0, fmt.Errorf("encountered pre-OS application launch when SysPrep applications are not supported: %v", 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 log event type %v", ev.EventType)
+ }
+ }
+
+ // 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)
+ }
+
+ // Iterate over the OS-present events until we hit the boot option launch, making sure that
+ // if there is a firmware agent launch before this then it is Absolute, else the profile
+ // generation for PCR4 will generate the wrong values.
+ for len(events) > 0 {
+ ev := events[0]
+ events = events[1:]
+
+ if ev.PCRIndex != internal_efi.BootManagerCodePCR {
+ // Not PCR4
+ continue
+ }
+
+ if ev.EventType != tcglog.EventTypeEFIBootServicesApplication {
+ // 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", ev.EventType)
+ }
+
+ isBootOptLaunch, err := isLaunchedFromLoadOption(ev, bootOpt)
+ if err != nil {
+ return 0, fmt.Errorf("cannot determine if OS-present application launch is from a boot load option: %w", err)
+ }
+ if isBootOptLaunch {
+ // We have the EV_EFI_BOOT_SERVICES_APPLICATION event associated with the initial OS
+ // component launch. This branch always causes this loop to exit.
+ // Make sure that the measured digest matches the expected Authenticode digest, computed
+ // from the first supplied boot image related to the current boot.
+ image := loadImages[0]
+ loadImages = loadImages[1:]
+
+ r, err := image.Open()
+ if err != nil {
+ return 0, fmt.Errorf("cannot open image %s: %w", image, err)
+ }
+ defer r.Close() // Yes, this will run on function exit rather than when we break from the loop
+
+ digest, err := efiComputePeImageDigest(pcrAlg.GetHash(), r, r.Size())
+ if err != nil {
+ return 0, fmt.Errorf("cannot compute digest of initial OS-present application %s: %w", image, err)
+ }
+ if !bytes.Equal(digest, ev.Digests[pcrAlg]) {
+ // There is a digest mismatch. Either the caller supplied images that aren't related
+ // to the current boot, or there's a firmware bug here.
+ return 0, fmt.Errorf("log contains unexpected digest for initial OS-present application %s (calculated: %#x, log value: %#x)", image, digest, ev.Digests[pcrAlg])
+ }
+
+ // We've found this initial OS load, so break from here
+ break
+ }
+
+ // We have an EV_EFI_BOOT_SERVICES_APPLICATION that didn't come from a load option.
+ // 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 digests 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 here.
+ if result&bootManagerCodeAbsoluteComputraceRunning > 0 {
+ return 0, fmt.Errorf("additional OS-present launch is not from a boot option but already established Absolute is running: %v", ev.Data)
+ }
+
+ isAbsolute, err := internal_efi.IsAbsoluteAgentLaunch(ev)
+ if err != nil {
+ return 0, fmt.Errorf("cannot determine if initial OS-present application launch is Absolute: %w", err)
+ }
+ if !isAbsolute {
+ // if ev.Data if not a *EFIImageLoadEvent then IsAbsoluteAgentLaunch returns an error
+ return 0, fmt.Errorf("initial OS-present launch is not from a boot option and is not Absolute: %v", ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath)
+ }
+
+ result |= bootManagerCodeAbsoluteComputraceRunning
+ }
+
+ // Make sure all of the remaining image launches match the digests of the images
+ // supplied. Images will either be loaded via the firmware's LoadImage API in
+ // which case the digest should be correct if the initial boot application was
+ // correct, or they go via a custom loader (eg, the shim protocol) that makes use
+ // of EFI_TCG2_PROTOCOL. If this isn't present, shim will fall back to EFI_TCG_PROTOCOL
+ // which doesn't allow computing of PE image digests and will result in flat file digests.
+ // If there are any more EV_EFI_BOOT_SERVICES_APPLICATION, make sure that we test at least
+ // one of them.
+ testedSBL := false
+ for len(events) > 0 {
+ ev := events[0]
+ events = events[1:]
+
+ if ev.PCRIndex != internal_efi.BootManagerCodePCR {
+ // Not PCR4
+ continue
+ }
+
+ if ev.EventType != tcglog.EventTypeEFIBootServicesApplication {
+ // Ignore these as we assume OS loaders are allowed to use other event
+ // types.
+ continue
+ }
+
+ // We have a EV_EFI_BOOT_SERVICES_APPLICATION event, so make sure the digest is as expected,
+ // based on the supplied load images for the current boot.
+ if len(loadImages) == 0 {
+ // We have no more supplied load images for the current boot.
+ if !testedSBL {
+ // If we haven't already at least tested something other than the IBL, return an error.
+ return 0, fmt.Errorf("cannot verify digest for %v event associated with at least one application other than the initial boot loader", tcglog.EventTypeEFIBootServicesApplication)
+ }
+ // We weren't supplied all of the boot images, but we have tested an event other than
+ // the one associated with the IBL. That's probably good enough.
+ result |= bootManagerCodeNotAllLaunchDigestsVerified
+ return result, nil
+ }
+
+ testedSBL = true
+
+ 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 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 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..60446dde
--- /dev/null
+++ b/efi/preinstall/check_pcr4_test.go
@@ -0,0 +1,650 @@
+// -*- 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.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) 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: 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.IsFalse)
+}
+
+func (s *pcr4Suite) TestIsLaunchedFromLoadOptionWrongData(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: &invalidEventData{errors.New("some error")},
+ }
+
+ _, err := IsLaunchedFromLoadOption(ev, opt)
+ c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has wrong data format: some error`)
+}
+
+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, `empty device path for image load event`)
+}
+
+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, `initial OS-present launch is not from a boot option and is not Absolute: \\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`)
+}
+
+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 at least one application other than the initial 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 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 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 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 application launch when SysPrep applications are not supported: \\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`)
+}
+
+// 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.
+// - An event other than EV_SEPARATOR after EV_EFI_ACTION "Calling EFI Application from Boot Option".
+// - 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 01b4d509..c9a1cfd9 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
CheckTPM2DeviceFlags = checkTPM2DeviceFlags
CpuVendor = cpuVendor
DetectVirtResult = detectVirtResult
@@ -29,6 +35,9 @@ type (
)
const (
+ BootManagerCodeSysprepAppsPresent = bootManagerCodeSysprepAppsPresent
+ BootManagerCodeAbsoluteComputraceRunning = bootManagerCodeAbsoluteComputraceRunning
+ BootManagerCodeNotAllLaunchDigestsVerified = bootManagerCodeNotAllLaunchDigestsVerified
CheckTPM2DeviceInVM = checkTPM2DeviceInVM
CheckTPM2DevicePostInstall = checkTPM2DevicePostInstall
CpuVendorIntel = cpuVendorIntel
@@ -46,6 +55,7 @@ const (
var (
CalculateIntelMEFamily = calculateIntelMEFamily
+ CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements
CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR
CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements
CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank
@@ -56,7 +66,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/internal/efitest/log.go b/internal/efitest/log.go
index d58ce3f7..9c7b6236 100644
--- a/internal/efitest/log.go
+++ b/internal/efitest/log.go
@@ -386,8 +386,9 @@ 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[0:], 0)
binary.LittleEndian.PutUint16(order[2:], 1)
builder.hashLogExtendEvent(c, bytesHashData(order[:]), &logEvent{
pcrIndex: 1,
@@ -431,7 +432,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log {
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{0x5c, 0x00, 0x66, 0x00, 0x77, 0x00, 0x75, 0x00, 0x70, 0x00, 0x64, 0x00, 0x78, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x66, 0x00, 0x69, 0x00, 0x00, 0x00}}
+ OptionalData: []byte{0x04, 0x04, 0x22, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x66, 0x00, 0x77, 0x00, 0x75, 0x00, 0x70, 0x00, 0x64, 0x00, 0x78, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x66, 0x00, 0x69, 0x00, 0x00, 0x00}}
optionBytes, err := option.Bytes()
c.Assert(err, IsNil)
builder.hashLogExtendEvent(c, option, &logEvent{
@@ -442,6 +443,28 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log {
UnicodeName: "Boot0001",
VariableData: optionBytes}})
}
+ {
+ option := &efi.LoadOption{
+ Attributes: 1,
+ Description: "UEFI PM9A1 NVMe Samsung 2048GB",
+ 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\\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 {