From b77b4811f15cd86b0e5ad39e97ed66a6d775fa21 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 9 Aug 2024 15:08:16 +0100 Subject: [PATCH] preinstall: Add checks for PCR4 This adds checks some checks for PCR4. The caller supplies a context.Context to which an EFI variable backend is attached, a internal_efi.HostEnvironment implementation, a TCG log, a PCR digest algorithm (the optimum for this is computed earlier by another function that does a more general check of the TCG log) and a list of boot images related to the current boot. First of all, in the pre-OS environment, it checks that the log only contains either a single EV_OMIT_BOOT_DEVICE_EVENTS event or a single EV_EFI_ACTION "Calling EFI Application from Boot Option" event (after which, it expects to see the EV_SEPARATOR event). It also permits EV_EFI_BOOT_SERVICES_APPLICATION events in the pre-OS environment if the BootOptionSupport EFI variable indicates that system preparation applications are supported, although these must be before the previously mentioned EV_EFI_ACTION event. The profile generating code in efi/fw_load_handler.go copies these pre-OS events (including sysprep applications to the final policy). Note that firmware that implements v1.06 of the TCG PC-Client PFP spec implements another EV_EFI_ACTION event that indicates the number of the Boot option that the load occurred from - this code will need to include support for that quite soon. There is a corresponding github issue for this (https://github.com/snapcore/secboot/issues/308). Before processing the OS-present part of the log, it reads the BootCurrent EFI variable and matches this to a EFI_LOAD_OPTION from the TCG log - it uses the log as load options can be updated at runtime and might be out of date when this code runs. When processing the initial part of the OS-present section of the log, it expects to find the EV_EFI_BOOT_SERVICES_APPLICATION for the initial boot loader, and it expects the path of this to match the previously discovered load option - note that it doesn't expect a complete match. See how path matching works in efi.DevicePath.Matches. Any other event type at this stage is rejected as an error, as it would result in an invalid policy being generated by the code in efi/fw_load_handler.go. If the first EV_EFI_BOOT_SERVICES_APPLICATION event is not the initial boot loader, it checks to see if it is part of Absolute, in which case it skips it and looks for the next event. Note that Absolute will be copied to the computed policy if it is present by efi/fw_load_handler.go, altohugh it's recommended that this is disabled instead. The next event must be the initial boot loader, matched to the EFI_LOAD_OPTION that the BootCurrent EFI variable points to, else an error is returned. It verifies that the digest in the log matches the Authenticode digest of the first boot file supplied to the function. It then continues to process EV_EFI_BOOT_SERVICE_APPLICATION events in the log (permitting, but ignoring other event types - it's expected that if other OS boot code uses other event types in the log, the policy generation code for that OS component in the secboot efi package takes this into account). For each launch event (EV_EFI_BOOT_SERVICES_APPLICATION), it verifies that the digest in the log matches the Authenticode digest of the next boot file supplied. Note that it is not mandatory to verify every one of these events, but the function does require that the digests of at least one EV_EFI_BOOT_SERVICES_APPLICATION event other than the IBL is verified. This ensures that if the IBL loads the SBL outside of the LoadImage service (ie, how shim loads grub), that the EFI_TCG2_PROTOCOL interface is properly supported by the firmware to allow the IBL (ie, shim) to properly measure the Authenticode digest of the SBL (ie, grub). This isn't a test against OS components, which is is now what these tests are for - it's a test against firmware behaviour and firmware features. On success, the function will return a set of flags indicating whether system preparation applications were detected to be running, whether Absolute was detected to be running, or whether it wasn't able to verify all EV_EFI_BOOT_SERVICES_APPLICATION event digests because not all paths to every component for the current boot were supplied. What happens with these flags will be customizable with flags supplied to the higher level RunChecks API. --- efi/preinstall/check_pcr4.go | 410 +++++++++++++++++++ efi/preinstall/check_pcr4_test.go | 650 ++++++++++++++++++++++++++++++ efi/preinstall/export_test.go | 20 + internal/efitest/log.go | 27 +- 4 files changed, 1105 insertions(+), 2 deletions(-) create mode 100644 efi/preinstall/check_pcr4.go create mode 100644 efi/preinstall/check_pcr4_test.go 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 b1501b1f..02c00848 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -371,8 +371,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, @@ -416,7 +417,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{ @@ -427,6 +428,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 {