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 {