diff --git a/efi/preinstall/check_pcr4.go b/efi/preinstall/check_pcr4.go new file mode 100644 index 00000000..0909f284 --- /dev/null +++ b/efi/preinstall/check_pcr4.go @@ -0,0 +1,410 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "strings" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" + "github.com/canonical/tcglog-parser" + secboot_efi "github.com/snapcore/secboot/efi" + internal_efi "github.com/snapcore/secboot/internal/efi" +) + +var ( + efiComputePeImageDigest = efi.ComputePeImageDigest +) + +// readLoadOptionFromLog reads the corresponding Boot#### load option from the log, +// which reflects the value of it at boot time, as opposed to reading it from an +// EFI variable which may have been modified. +func readLoadOptionFromLog(log *tcglog.Log, n uint16) (*efi.LoadOption, error) { + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.PlatformFirmwareConfigPCR { + continue + } + if ev.EventType == tcglog.EventTypeSeparator { + // BootXXXX variables are measured during pre-OS, so we can bail here. + break + } + + if ev.EventType != tcglog.EventTypeEFIVariableBoot && ev.EventType != tcglog.EventTypeEFIVariableBoot2 { + // not a boot variable + continue + } + + data, ok := ev.Data.(*tcglog.EFIVariableData) + if !ok { + // decode error data is guaranteed to implement the error interface + return nil, fmt.Errorf("boot variable measurement has wrong data format: %w", ev.Data.(error)) + } + if data.VariableName != efi.GlobalVariable { + // not a global variable + continue + } + if !strings.HasPrefix(data.UnicodeName, "Boot") || len(data.UnicodeName) != 8 { + // name has unexpected prefix or length + continue + } + + var x uint16 + if y, err := fmt.Sscanf(data.UnicodeName, "Boot%x", &x); err != nil || y != 1 { + continue + } + if x != n { + // wrong load option + continue + } + + // We've found the correct load option. Decode it from the data stored in the log. + opt, err := efi.ReadLoadOption(bytes.NewReader(data.VariableData)) + if err != nil { + return nil, fmt.Errorf("cannot read load option from event data: %w", err) + } + return opt, nil + } + + return nil, errors.New("cannot find specified boot option") +} + +func isLaunchedFromLoadOption(ev *tcglog.Event, opt *efi.LoadOption) (yes bool, err error) { + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + // The caller should check this. + panic("unexpected event type") + } + data, ok := ev.Data.(*tcglog.EFIImageLoadEvent) + if !ok { + // the data resulting from decode errors is guaranteed to implement error + return false, fmt.Errorf("EV_EFI_BOOT_SERVICES_APPLICATION event has wrong data format: %w", ev.Data.(error)) + } + if len(data.DevicePath) == 0 { + return false, errors.New("empty device path for image load event") + } + + // Try to match the load option. + if opt.Attributes&efi.LoadOptionActive == 0 { + return false, errors.New("boot option is not active") + } + if data.DevicePath.Matches(opt.FilePath) != efi.DevicePathNoMatch { + // On the balance of probabilities, this is most likely a launch of the + // load option, given that we have some sort of path match with it. + return true, nil + } + + return false, nil +} + +type bootManagerCodeResultFlags int + +const ( + bootManagerCodeSysprepAppsPresent bootManagerCodeResultFlags = 1 << iota + bootManagerCodeAbsoluteComputraceRunning + bootManagerCodeNotAllLaunchDigestsVerified +) + +func checkBootManagerCodeMeasurements(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, loadImages []secboot_efi.Image) (result bootManagerCodeResultFlags, err error) { + if len(loadImages) == 0 { + return 0, errors.New("at least the initial EFI application loaded during this boot must be supplied") + } + varCtx := env.VarContext(ctx) + + opts, err := efi.ReadBootOptionSupportVariable(varCtx) + if err != nil { + return 0, fmt.Errorf("cannot obtain boot option support: %w", err) + } + sysprepSupported := opts&efi.BootOptionSupportSysPrep > 0 + + // Iterate over the log until OS-present and make sure we have expected + // event types. + var expectingSeparator bool + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + // Not PCR4 + continue + } + if ev.EventType == tcglog.EventTypeSeparator { + // Bail out here at the transition to OS-present. + // We replay this from the log so we don't need to verify the consistency between the + // event data and digests. + break + } + if expectingSeparator { + // We should have bailed out of the pre-OS part of the testing. + return 0, fmt.Errorf("unexpected event type %v: expecting transition from pre-OS to OS-present event", ev.EventType) + } + + omitBootDeviceEvents := false + switch { + case ev.EventType == tcglog.EventTypeOmitBootDeviceEvents: + // The event data should be "BOOT ATTEMPTS OMITTED" and the event digests should + // be the tagged hash of this, but we don't verify that because this event just gets + // copied to the profile without any prediction. If we support any prediction of this + // event in the future, eg, by toggling firmware settings, then we should definitely + // verify this event's data is consistent with its digests. + if omitBootDeviceEvents { + return 0, fmt.Errorf("already seen a %v event", ev.EventType) + } + omitBootDeviceEvents = true + case ev.EventType == tcglog.EventTypeEFIAction: + // ok, although 1.05 of the TCG PFP spec is a bit ambiguous here, section 8.2.4 says + // the event associated with the first boot attempt, if it is measured, occurs before + // the separator (as part of pre-OS). The actual PCR usage section 3.3.4.5 in this version + // of the spec and older contradicts this and mentions a bunch of EV_ACTION events that + // pertain to BIOS boot. On every device we've tested, this event occurs before the + // separator and there are no BIOS boot related EV_ACTION events. 1.06 of the TCG PFP + // spec tries to clean this up a bit, removing reference to the EV_ACTION events and + // correcting the "Method for measurement" subsection of section 3.3.4.5 to match + // section 8.2.4. We reject any EV_ACTION events in PCR4 here anyway. + // + // EV_EFI_ACTION event digests are the tagged hash of the event data, although we don't + // test that here because we just copy the events to the profile without any prediction. + // If we support any prediction of these specific events in the future, eg, by toggling + // firmware settings, then we should definitely verify that the event data is consistent + // with the event digests. + if ev.Data == tcglog.EFICallingEFIApplicationEvent { + // This is the signal from BDS that we're about to hand over to the OS. + + if omitBootDeviceEvents { + return 0, fmt.Errorf("unexpected %v event %q (because of earlier %v event)", ev.EventType, ev.Data, tcglog.EventTypeOmitBootDeviceEvents) + } + + // The next event we're expecting is the pre-OS to OS-present transition. + // + // TODO(chrisccoulson): The TCG PFP spec 1.06 r49 expects there to be a + // EV_EFI_ACTION event immediately following this one with the string + // "Booting to Option". Whilst the current profile generation code + // will preserve what's currently in the log, there needs to be an API for boot + // configuration code to specificy the actual boot option to ensure that we + // predict the correct value. We currently fail support for PCR4 if this + // unsupported EV_EFI_ACTION event is present next. + expectingSeparator = true + } else { + // We're not expecting any other EV_EFI_ACTION event types. + return 0, fmt.Errorf("unexpected %s event %q", ev.EventType, ev.Data) + } + case ev.EventType == tcglog.EventTypeEFIBootServicesApplication: + // Assume all pre-OS application launches are SysPrep applications. There shouldn't + // really be anything else here and there isn't really a reliable way to detect. + // It might be possible to match the device path with the next variable in SysPrepOrder, + // but these can be modified at runtime to not reflect what they were at boot time, + // and SysPrep variables are not measured to the TCG log. + // + // As we don't do any prediction of sysprep applications (yet - never say never), we + // don't verify that the measured Authenticode digest matches the binary at the end of the + // device path, if it's reachable from the OS. Although this also suffers from a similar + // variation of the issue described above - that path could have been updated between + // booting and now. + if !sysprepSupported { + // The firmware indicated that sysprep applications aren't supported yet it still + // loaded one! + if err, isErr := ev.Data.(error); isErr { + return 0, fmt.Errorf("encountered pre-OS application launch when SysPrep applications are not supported, and unable to decode UEFI_IMAGE_LOAD: %w", err) + } + return 0, fmt.Errorf("encountered pre-OS application launch when SysPrep applications are not supported: %v", ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath) + + } + result |= bootManagerCodeSysprepAppsPresent // Record that this boot contains system preparation applications. + default: + // We're not expecting any other event types during the pre-OS phase. + return 0, fmt.Errorf("unexpected pre-OS log event type %v", ev.EventType) + } + } + + // Obtain the BootCurrent variable and use this to obtain the corresponding load entry + // that was measured to the log. BootXXXX variables are measured to the TPM and so we don't + // need to read back from an EFI variable that could have been modified between boot time + // and now. + current, err := efi.ReadBootCurrentVariable(varCtx) + if err != nil { + return 0, fmt.Errorf("cannot read BootCurrent variable: %w", err) + } + bootOpt, err := readLoadOptionFromLog(log, current) + if err != nil { + return 0, fmt.Errorf("cannot read current Boot%04x load option from log: %w", current, err) + } + + // Iterate over the OS-present events until we hit the boot option launch, making sure that + // if there is a firmware agent launch before this then it is Absolute, else the profile + // generation for PCR4 will generate the wrong values. + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + // Not PCR4 + continue + } + + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + // The only events we're expecting in OS-present for now is EV_EFI_BOOT_SERVICES_APPLICATION. + return 0, fmt.Errorf("unexpected OS-present log event type %v", ev.EventType) + } + + isBootOptLaunch, err := isLaunchedFromLoadOption(ev, bootOpt) + if err != nil { + return 0, fmt.Errorf("cannot determine if OS-present application launch is from a boot load option: %w", err) + } + if isBootOptLaunch { + // We have the EV_EFI_BOOT_SERVICES_APPLICATION event associated with the initial OS + // component launch. This branch always causes this loop to exit. + // Make sure that the measured digest matches the expected Authenticode digest, computed + // from the first supplied boot image related to the current boot. + image := loadImages[0] + loadImages = loadImages[1:] + + r, err := image.Open() + if err != nil { + return 0, fmt.Errorf("cannot open image %s: %w", image, err) + } + defer r.Close() // Yes, this will run on function exit rather than when we break from the loop + + digest, err := efiComputePeImageDigest(pcrAlg.GetHash(), r, r.Size()) + if err != nil { + return 0, fmt.Errorf("cannot compute digest of initial OS-present application %s: %w", image, err) + } + if !bytes.Equal(digest, ev.Digests[pcrAlg]) { + // There is a digest mismatch. Either the caller supplied images that aren't related + // to the current boot, or there's a firmware bug here. + return 0, fmt.Errorf("log contains unexpected digest for initial OS-present application %s (calculated: %#x, log value: %#x)", image, digest, ev.Digests[pcrAlg]) + } + + // We've found this initial OS load, so break from here + break + } + + // We have an EV_EFI_BOOT_SERVICES_APPLICATION that didn't come from a load option. + // Test to see if it's part of Absolute. If it is, that's fine - we copy this into + // the profile, so we don't need to do any other verification of it and we don't have + // anything to verify the Authenticode digests against anyway. We have a device path, + // but not one that we're able to read back from. + // + // If this isn't Absolute, we bail with an error. We don't support anything else here. + if result&bootManagerCodeAbsoluteComputraceRunning > 0 { + return 0, fmt.Errorf("additional OS-present launch is not from a boot option but already established Absolute is running: %v", ev.Data) + } + + isAbsolute, err := internal_efi.IsAbsoluteAgentLaunch(ev) + if err != nil { + return 0, fmt.Errorf("cannot determine if initial OS-present application launch is Absolute: %w", err) + } + if !isAbsolute { + // if ev.Data if not a *EFIImageLoadEvent then IsAbsoluteAgentLaunch returns an error + return 0, fmt.Errorf("initial OS-present launch is not from a boot option and is not Absolute: %v", ev.Data.(*tcglog.EFIImageLoadEvent).DevicePath) + } + + result |= bootManagerCodeAbsoluteComputraceRunning + } + + // Make sure all of the remaining image launches match the digests of the images + // supplied. Images will either be loaded via the firmware's LoadImage API in + // which case the digest should be correct if the initial boot application was + // correct, or they go via a custom loader (eg, the shim protocol) that makes use + // of EFI_TCG2_PROTOCOL. If this isn't present, shim will fall back to EFI_TCG_PROTOCOL + // which doesn't allow computing of PE image digests and will result in flat file digests. + // If there are any more EV_EFI_BOOT_SERVICES_APPLICATION, make sure that we test at least + // one of them. + testedSBL := false + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + // Not PCR4 + continue + } + + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + // Ignore these as we assume OS loaders are allowed to use other event + // types. + continue + } + + // We have a EV_EFI_BOOT_SERVICES_APPLICATION event, so make sure the digest is as expected, + // based on the supplied load images for the current boot. + if len(loadImages) == 0 { + // We have no more supplied load images for the current boot. + if !testedSBL { + // If we haven't already at least tested something other than the IBL, return an error. + return 0, fmt.Errorf("cannot verify digest for %v event associated with at least one application other than the initial boot loader", tcglog.EventTypeEFIBootServicesApplication) + } + // We weren't supplied all of the boot images, but we have tested an event other than + // the one associated with the IBL. That's probably good enough. + result |= bootManagerCodeNotAllLaunchDigestsVerified + return result, nil + } + + testedSBL = true + + image := loadImages[0] + loadImages = loadImages[1:] + + err := func() error { + r, err := image.Open() + if err != nil { + return fmt.Errorf("cannot open image %s: %w", image, err) + } + defer r.Close() + + digest, err := efiComputePeImageDigest(pcrAlg.GetHash(), r, r.Size()) + if err != nil { + return fmt.Errorf("cannot compute Authenticode digest of OS-present application %s: %w", image, err) + } + if bytes.Equal(digest, ev.Digests[pcrAlg]) { + // The PE digest of the application matches what's in the log, so we're all good. + return nil + } + + // Digest in log does not match PE image digest. Compute flat-file digest and compare against that + // for diagnostic purposes. + r2 := io.NewSectionReader(r, 0, r.Size()) + h := pcrAlg.NewHash() + if _, err := io.Copy(h, r2); err != nil { + return fmt.Errorf("cannot compute flat file digest of OS-present application %s: %w", image, err) + } + if !bytes.Equal(h.Sum(nil), ev.Digests[pcrAlg]) { + // Still no digest match + return fmt.Errorf("log contains unexpected digest for OS-present application %s (calculated PE digest: %#x, log value: %#x) - were the correct boot images supplied?", image, digest, ev.Digests[pcrAlg]) + } + // We have a digest match, so something loaded this component outside of the LoadImage API and used the + // legacy EFI_TCG_PROTOCOL API to measure it, or used the proper EFI_TCG2_PROTOCOL API without the + // PE_COFF_IMAGE flag. In any case, WithBootManagerCodeProfile() will mis-predict the loading of this. + return fmt.Errorf("log contains unexpected digest for OS-present application %s: log digest matches flat file digest (%#x) which suggests an image loaded outside of the LoadImage API and firmware lacking support for the EFI_TCG2_PROTOCOL and/or the PE_COFF_IMAGE flag", image, h.Sum(nil)) + }() + if err != nil { + return 0, err + } + } + + return result, nil +} diff --git a/efi/preinstall/check_pcr4_test.go b/efi/preinstall/check_pcr4_test.go new file mode 100644 index 00000000..60446dde --- /dev/null +++ b/efi/preinstall/check_pcr4_test.go @@ -0,0 +1,650 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall_test + +import ( + "context" + "crypto" + "errors" + "io" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" + "github.com/canonical/tcglog-parser" + secboot_efi "github.com/snapcore/secboot/efi" + . "github.com/snapcore/secboot/efi/preinstall" + internal_efi "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/efitest" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type pcr4Suite struct{} + +var _ = Suite(&pcr4Suite{}) + +func (s *pcr4Suite) TestReadLoadOptionFromLog(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + opt, err := ReadLoadOptionFromLog(log, 3) + c.Assert(err, IsNil) + c.Check(opt, DeepEquals, &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + OptionalData: []byte{}, + }) +} + +func (s *pcr4Suite) TestReadLoadOptionFromLogNotExist(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{}) + _, err := ReadLoadOptionFromLog(log, 10) + c.Check(err, ErrorMatches, `cannot find specified boot option`) +} + +func (s *pcr4Suite) TestIsLaunchedFromLoadOptionGood(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsTrue) +} + +func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNoMatch(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x1e482b5b, 0x6600, 0x427f, 0xb394, [...]uint8{0x9a, 0x68, 0x82, 0x3e, 0x55, 0x04})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + yes, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, IsNil) + c.Check(yes, testutil.IsFalse) +} + +func (s *pcr4Suite) TestIsLaunchedFromLoadOptionWrongData(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &invalidEventData{errors.New("some error")}, + } + + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has wrong data format: some error`) +} + +func (s *pcr4Suite) TestIsLaunchedFromLoadOptionEmptyDevicePath(c *C) { + opt := &efi.LoadOption{ + Attributes: 1, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{}, + }, + } + + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `empty device path for image load event`) +} + +func (s *pcr4Suite) TestIsLaunchedFromLoadOptionNotActive(c *C) { + opt := &efi.LoadOption{ + Attributes: 0, + Description: "ubuntu", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + } + ev := &tcglog.Event{ + PCRIndex: internal_efi.BootManagerCodePCR, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0x6556c018, + LengthInMemory: 955072, + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x1d}, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0}, + &efi.NVMENamespaceDevicePathNode{ + NamespaceID: 0x1, + NamespaceUUID: 0x0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi"), + }, + }, + } + + _, err := IsLaunchedFromLoadOption(ev, opt) + c.Check(err, ErrorMatches, `boot option is not active`) +} + +type mockImageReader struct { + contents []byte + digest tpm2.Digest + closed bool +} + +func (r *mockImageReader) ReadAt(data []byte, offset int64) (int, error) { + copy(data, r.contents[offset:]) + return len(data), nil +} + +func (r *mockImageReader) Close() error { + if r.closed { + return errors.New("already closed") + } + r.closed = true + return nil +} + +func (r *mockImageReader) Size() int64 { + return int64(len(r.contents)) +} + +type mockImage struct { + contents []byte // Used to produce a flat-file digest + digest tpm2.Digest // Authenticode digest +} + +func (i *mockImage) String() string { + return "mock image" +} + +func (i *mockImage) Open() (secboot_efi.ImageReader, error) { + return &mockImageReader{ + contents: i.contents, + digest: i.digest}, nil +} + +type testCheckBootManagerCodeMeasurementsParams struct { + env internal_efi.HostEnvironment + pcrAlg tpm2.HashAlgorithmId + images []secboot_efi.Image + expectedResult BootManagerCodeResultFlags +} + +func (s *pcr4Suite) testCheckBootManagerCodeMeasurements(c *C, params *testCheckBootManagerCodeMeasurementsParams) error { + log, err := params.env.ReadEventLog() + c.Assert(err, IsNil) + + i := 0 + restore := MockEfiComputePeImageDigest(func(alg crypto.Hash, r io.ReaderAt, sz int64) ([]byte, error) { + c.Check(alg, Equals, params.pcrAlg.GetHash()) + c.Assert(r, testutil.ConvertibleTo, &mockImageReader{}) + imageReader := r.(*mockImageReader) + c.Assert(params.images[i], testutil.ConvertibleTo, &mockImage{}) + c.Check(sz, Equals, int64(len(params.images[i].(*mockImage).contents))) + i += 1 + return imageReader.digest, nil + }) + defer restore() + + result, err := CheckBootManagerCodeMeasurements(context.Background(), params.env, log, params.pcrAlg, params.images) + if err != nil { + return err + } + c.Check(result, Equals, params.expectedResult) + return nil +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodSHA256(c *C) { + // Test good result with SHA-256 + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + expectedResult: 0, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodSHA384(c *C) { + // Test good result with SHA-384, when log also contains SHA-256 + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA384, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "030ac3c913dab858f1d69239115545035cff671d6229f95577bb0ffbd827b35abaf6af6bfd223e04ecc9b60a9803642d")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "6c2df9007211786438be210b6908f2935d0b25ebdcd2c65621826fd2ec55fb9fbacbfe080d48db98f0ef970273b8254a")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "42f61b3089f5ce0646b422a59c9632065db2630f3e5b01690e63c41420ed31f10ff2a191f3440f9501109fc85f7fb00f")}, + }, + expectedResult: 0, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithSysprepApp(c *C) { + // Test good result with sysprep application in log before OS-present + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeSysPrepAppLaunch: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + expectedResult: BootManagerCodeSysprepAppsPresent, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithAbsolute(c *C) { + // Test good result with Absolute running as part of OS-present before shim + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d}), + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + expectedResult: BootManagerCodeAbsoluteComputraceRunning, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithMissingImages(c *C) { + // Test good result with missing boot images - the function needs at least the IBL (in our case, shim) and SBL (in our case, grub) + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + }, + expectedResult: BootManagerCodeNotAllLaunchDigestsVerified, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsGoodWithoutCallingEFIApplicationEvent(c *C) { + // Test good result with log without EV_EFI_ACTION "Calling EFI Application from Boot Option" event + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + NoCallingEFIApplicationEvent: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + expectedResult: 0, + }) + c.Check(err, IsNil) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadNoImages(c *C) { + // Test error result because no load images were supplied + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + }) + c.Check(err, ErrorMatches, `at least the initial EFI application loaded during this boot must be supplied`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadBootOptionSupport(c *C) { + // Test error result because of invalid BootOptionSupport + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `cannot obtain boot option support: variable contents has an unexpected size \(5 bytes\)`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadBootCurrent(c *C) { + // Test error result because of bad BootCurrent + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `cannot read BootCurrent variable: BootCurrent variable contents has the wrong size \(1 bytes\)`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadLoadOption(c *C) { + // Test error result because BootCurrent value doesn't match entry in log + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x5, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `cannot read current Boot0005 load option from log: cannot find specified boot option`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadMissingInitialLaunch(c *C) { + // Test error result because IBL launch can't be identified - it doesn't match boot entry in log that BootCurrent points to + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `initial OS-present launch is not from a boot option and is not Absolute: \\PciRoot\(0x0\)\\Pci\(0x1d,0x0\)\\Pci\(0x0,0x0\)\\NVMe\(0x1,00-00-00-00-00-00-00-00\)\\HD\(1,GPT,66de947b-fdb2-4525-b752-30d66bb2b960\)\\\\EFI\\ubuntu\\shimx64\.efi`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadMissingSBL(c *C) { + // Test error result because it wasn't possible to verify Authenticode digest for SBL launch (in our case, grub), as it wasn't supplied + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `cannot verify digest for EV_EFI_BOOT_SERVICES_APPLICATION event associated with at least one application other than the initial boot loader`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadSBLMeasuredFlatFileDigest_NoEFITCG2Protocol(c *C) { + // Test error result because digest associated with SBL launch matches the file digest rather than the Authenticode digest, which in the + // case of shim -> grub, might mean that EFI_TCG2_PROTOCOL is missing from the firmware. + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + // We have to cheat a bit here because the digest is hardcoded in the test log. We set an invalid Authenticode digest for the image so the intial test fails + // and then have the following code digest the same string that produces the log digest ("mock grub executable"), to get a digest that matches what's in the + // log so the test thinks that the log contains the flat file digest. + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd")}, + }, + }) + c.Check(err, ErrorMatches, `log contains unexpected digest for OS-present application mock image: log digest matches flat file digest \(0xd5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0\) which suggests an image loaded outside of the LoadImage API and firmware lacking support for the EFI_TCG2_PROTOCOL and/or the PE_COFF_IMAGE flag`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadProvidedBootImages(c *C) { + // Test error result because the SBL image (in our case grub) has a digest that doesn't match what's in the log + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + // In this case, we provide an invalid Authenticode digest, and a payload that also subsequently produces the wrong file digest. + &mockImage{contents: []byte("foo"), digest: testutil.DecodeHexString(c, "80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd")}, + }, + }) + c.Check(err, ErrorMatches, `log contains unexpected digest for OS-present application mock image \(calculated PE digest: 0x80fd5a9364df79953369758a419f7cb167201cf580160b91f837aad455c55bcd, log value: 0xd5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0\) - were the correct boot images supplied\?`) +} + +func (s *pcr4Suite) TestCheckBootManagerCodeMeasurementsBadWithUnsupportedSysprepApp(c *C) { + // Test error result because a sysprep app was detected when BootOptionSupport indicates they aren't supported + err := s.testCheckBootManagerCodeMeasurements(c, &testCheckBootManagerCodeMeasurementsParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithMockVars(efitest.MockVars{ + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x03, 0x03, 0x00, 0x00}}, + }), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + IncludeSysPrepAppLaunch: true, + })), + ), + pcrAlg: tpm2.HashAlgorithmSHA256, + images: []secboot_efi.Image{ + &mockImage{contents: []byte("mock shim executable"), digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7")}, + }, + }) + c.Check(err, ErrorMatches, `encountered pre-OS application launch when SysPrep applications are not supported: \\PciRoot\(0x0\)\\Pci\(0x1d,0x0\)\\Pci\(0x0,0x0\)\\NVMe\(0x1,00-00-00-00-00-00-00-00\)\\HD\(1,GPT,66de947b-fdb2-4525-b752-30d66bb2b960\)\\\\EFI\\Dell\\sysprep.efi`) +} + +// TODO (other error cases - harder): +// - Unexpected event types in log in pre-OS environment. +// - More than one EV_OMIT_BOOT_DEVICE_EVENTS event in log in pre-OS environment. +// - EV_EFI_ACTION "Calling EFI Application from Boot Option" along with EV_OMIT_BOOT_DEVICE_EVENTS event. +// - An event other than EV_SEPARATOR after EV_EFI_ACTION "Calling EFI Application from Boot Option". +// - Event other than EV_EFI_BOOT_SERVICES_APPLICATION in initial OS-present environment (ie, after EV_SEPARATOR). +// - Image open errors. +// - efi.ComputePeImageDigest errors. +// - EV_EFI_BOOT_SERVICES_APPLICATION event after detecting Absolute, and which is not associated with the IBL launch (doesn't match boot option) +// - internal_efi.IsAbsoluteAgentLaunch error. diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index 01b4d509..c9a1cfd9 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -19,7 +19,13 @@ package preinstall +import ( + "crypto" + "io" +) + type ( + BootManagerCodeResultFlags = bootManagerCodeResultFlags CheckTPM2DeviceFlags = checkTPM2DeviceFlags CpuVendor = cpuVendor DetectVirtResult = detectVirtResult @@ -29,6 +35,9 @@ type ( ) const ( + BootManagerCodeSysprepAppsPresent = bootManagerCodeSysprepAppsPresent + BootManagerCodeAbsoluteComputraceRunning = bootManagerCodeAbsoluteComputraceRunning + BootManagerCodeNotAllLaunchDigestsVerified = bootManagerCodeNotAllLaunchDigestsVerified CheckTPM2DeviceInVM = checkTPM2DeviceInVM CheckTPM2DevicePostInstall = checkTPM2DevicePostInstall CpuVendorIntel = cpuVendorIntel @@ -46,6 +55,7 @@ const ( var ( CalculateIntelMEFamily = calculateIntelMEFamily + CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank @@ -56,7 +66,17 @@ var ( CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings DetectVirtualization = detectVirtualization DetermineCPUVendor = determineCPUVendor + IsLaunchedFromLoadOption = isLaunchedFromLoadOption OpenAndCheckTPM2Device = openAndCheckTPM2Device ReadIntelHFSTSRegistersFromMEISysfs = readIntelHFSTSRegistersFromMEISysfs ReadIntelMEVersionFromMEISysfs = readIntelMEVersionFromMEISysfs + ReadLoadOptionFromLog = readLoadOptionFromLog ) + +func MockEfiComputePeImageDigest(fn func(crypto.Hash, io.ReaderAt, int64) ([]byte, error)) (restore func()) { + orig := efiComputePeImageDigest + efiComputePeImageDigest = fn + return func() { + efiComputePeImageDigest = orig + } +} diff --git a/internal/efitest/log.go b/internal/efitest/log.go index d58ce3f7..9c7b6236 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -386,8 +386,9 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { // Mock boot config measurements { - var order [4]uint8 + var order [6]uint8 binary.LittleEndian.PutUint16(order[0:], 3) + binary.LittleEndian.PutUint16(order[0:], 0) binary.LittleEndian.PutUint16(order[2:], 1) builder.hashLogExtendEvent(c, bytesHashData(order[:]), &logEvent{ pcrIndex: 1, @@ -431,7 +432,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), MBRType: efi.GPT}, efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi")}, - OptionalData: []byte{0x5c, 0x00, 0x66, 0x00, 0x77, 0x00, 0x75, 0x00, 0x70, 0x00, 0x64, 0x00, 0x78, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x66, 0x00, 0x69, 0x00, 0x00, 0x00}} + OptionalData: []byte{0x04, 0x04, 0x22, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x66, 0x00, 0x77, 0x00, 0x75, 0x00, 0x70, 0x00, 0x64, 0x00, 0x78, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x66, 0x00, 0x69, 0x00, 0x00, 0x00}} optionBytes, err := option.Bytes() c.Assert(err, IsNil) builder.hashLogExtendEvent(c, option, &logEvent{ @@ -442,6 +443,28 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { UnicodeName: "Boot0001", VariableData: optionBytes}}) } + { + option := &efi.LoadOption{ + Attributes: 1, + Description: "UEFI PM9A1 NVMe Samsung 2048GB", + FilePath: efi.DevicePath{ + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x100000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), + MBRType: efi.GPT}, + efi.FilePathDevicePathNode("\\EFI\\Boot\\BootX64.efi")}} + optionBytes, err := option.Bytes() + c.Assert(err, IsNil) + builder.hashLogExtendEvent(c, option, &logEvent{ + pcrIndex: 1, + eventType: tcglog.EventTypeEFIVariableBoot, + data: &tcglog.EFIVariableData{ + VariableName: efi.GlobalVariable, + UnicodeName: "Boot0000", + VariableData: optionBytes}}) + } // Mock boundary between pre-OS and OS-present if !opts.NoCallingEFIApplicationEvent {