diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index 2930e391..a43f593d 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -28,6 +28,7 @@ import ( efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" + "github.com/snapcore/secboot/efi/internal" "golang.org/x/xerrors" ) @@ -227,31 +228,82 @@ func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error // we've enabled follow section 8.2.4 when they measure the first EV_EFI_ACTION event (which is // optional - firmware should measure a EV_OMIT_BOOT_DEVICE_EVENTS event if they are not measured, // although some implementations don't do this either). I've not seen any implementations use the - // EV_ACTION events, and these would probably require explicit support here. + // mentioned EV_ACTION events, and these look like they are only relevant to BIOS boot anyway. + // + // The TCG PFP 1.06 r49 cleans this up a bit - it removes reference to the EV_ACTION events, and + // corrects the "Method for measurement" subsection of section 3.3.4.5 to describe that things work + // how we previously assumed. It does introduce a new EV_EFI_ACTION event ("Booting to Option") + // which will require explicit support in this package so it is currently rejected by the + // preinstall.RunChecks logic. // // This also retains measurements associated with the launch of any system preparation applications, // although note that the inclusion of these make a profile inherently fragile. The TCG PC Client PFP // spec v1.05r23 doesn't specify whether these are launched as part of the pre-OS environment or as // part of the OS-present environment. It defines the boundary between the pre-OS environment and // OS-present environment as a separator event measured to PCRs 0-7, but EDK2 measures a separator to - // PCR7 as soon as the secure boot policy is measured and system preparation applications are considered - // part of the pre-OS environment - they are measured to PCR4 before the pre-OS to OS-present transition - // is signalled by measuring separators to the remaining PCRs. This seems sensible, but newer Dell - // devices load an agent from firmware before shim is executed and measure this to PCR4 as part of the - // OS-present environment, which seems wrong. The approach here assumes that the EDK2 behaviour is - // correct. - for _, event := range h.log.Events { + // PCR7 as soon as the secure boot configuration is measured and system preparation applications are + // considered part of the pre-OS environment - they are measured to PCR4 before the pre-OS to OS-present + // transition is signalled by measuring separators to the remaining PCRs. The UEFI specification says that + // system preparation applications are executed before the ready to boot signal, which is when the transition + // from pre-OS to OS-present occurs, so I think we can be confident that we're correct here. + events := h.log.Events + measuredSeparator := false + for len(events) > 0 { + event := events[0] + events = events[1:] + if event.PCRIndex != tcglog.PCRIndex(bootManagerCodePCR) { continue } if event.EventType == tcglog.EventTypeSeparator { - return h.measureSeparator(ctx, bootManagerCodePCR, event) + if err := h.measureSeparator(ctx, bootManagerCodePCR, event); err != nil { + return err + } + measuredSeparator = true + break } ctx.ExtendPCR(bootManagerCodePCR, tpm2.Digest(event.Digests[ctx.PCRAlg()])) } - return errors.New("missing separator") + if !measuredSeparator { + return errors.New("missing separator") + } + + // Some newer laptops including those from Dell and Lenovo execute code from a firmware volume as part + // of the OS-present environment, before shim runs, and using the LoadImage API which results in an + // additional measurement to PCR4. Copy this into the profile if it's part of a well-known endpoint + // management application known as "Absolute" (formerly "Computrace"). Discard anything else which + // will result in an invalid profile but will be picked up by the preinstall.RunChecks API anyway. + for len(events) > 0 { + event := events[0] + events = events[1:] + + if event.PCRIndex != tcglog.PCRIndex(bootManagerCodePCR) { + continue + } + if event.EventType != tcglog.EventTypeEFIBootServicesApplication { + return fmt.Errorf("unexpected OS-present event type: %v", event.EventType) + } + + // once we encounter the first EV_EFI_BOOT_SERVICES_APPLICATION event in PCR4, this loop alway + // breaks or returns an error. + + isAbsolute, err := internal.IsAbsoluteAgentLaunch(event) + if err != nil { + return fmt.Errorf("encountered an error determining whether an OS-present launch is related to Absolute: %w", err) + } + if isAbsolute { + // copy the digest to the policy + ctx.ExtendPCR(bootManagerCodePCR, tpm2.Digest(event.Digests[ctx.PCRAlg()])) + } + // If it's not Absolute, we assume it's related to the OS launch which we will predict + // later on. If it's something else, discarding it here creates an invalid policy but this is + // picked up by the preinstall.RunChecks API anyway. + break + } + + return nil } // MeasureImageStart implements imageLoadHandler.MeasureImageStart. diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index 1204856a..5869e5bb 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -192,6 +192,69 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartBootManagerCodeProfileIncludeS }) } +func (s *fwLoadHandlerSuite) TestMeasureImageStartBootManagerCodeProfileIncludeAbsoluteAbtInstaller(c *C) { + // Verify the events associated with the "AbsoluteAbtInstaller" application contained in the firmware + // that loads as part of the OS-present. + vars := makeMockVars(c, withMsSecureBootConfig()) + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + vars: vars, + logOptions: &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d}), + }, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(BootManagerCodePCR), + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 4, eventType: mockPcrBranchResetEvent}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba")}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "59b1f92051a43fea7ac3a846f2714c3e041a4153d581acd585914bcff2ad2781")}, + }, + }) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartBootManagerCodeProfileIncludeAbsoluteComputraceInstaller(c *C) { + // Verify the events associated with the "AbsoluteComputraceInstaller" application contained in the firmware + // that loads as part of the OS-present. + vars := makeMockVars(c, withMsSecureBootConfig()) + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + vars: vars, + logOptions: &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x8feeecf1, 0xbcfd, 0x4a78, 0x9231, [...]byte{0x48, 0x01, 0x56, 0x6b, 0x35, 0x67}), + }, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(BootManagerCodePCR), + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 4, eventType: mockPcrBranchResetEvent}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba")}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "e58b9aa46c99806ce57c805a78d8224dd174743341e03e8a68b13a0071785295")}, + }, + }) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartBootManagerCodeProfileIgnoreUnknownFirmwareAgentLaunch(c *C) { + // Verify that the profile ignores any firmware application launch that isn't "AbsoluteAbtInstaller" or + // "AbsoluteComputraceInstaller". This will generate an invalid profile, but will be detected by the + // pre-install checks. + vars := makeMockVars(c, withMsSecureBootConfig()) + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + vars: vars, + logOptions: &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0xee993080, 0x5197, 0x4d4e, 0xb63c, [...]byte{0xf1, 0xf7, 0x41, 0x3e, 0x33, 0xce}), + }, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(BootManagerCodePCR), + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 4, eventType: mockPcrBranchResetEvent}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba")}, + {pcr: 4, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, + }, + }) +} + func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyAndBootManagerCodeProfile(c *C) { vars := makeMockVars(c, withMsSecureBootConfig()) s.testMeasureImageStart(c, &testFwMeasureImageStartData{ @@ -390,13 +453,13 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR0_1(c *C) { continue } // Overwrite the event data with a mock error event - log.Events[i].Data = &mockErrLogData{fmt.Errorf("cannot decode StartupLocality data: %w", io.EOF)} + log.Events[i].Data = &mockErrLogData{fmt.Errorf("cannot decode StartupLocality data: %w", io.ErrUnexpectedEOF)} break } } handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure platform firmware: cannot decode EV_NO_ACTION event data: cannot decode StartupLocality data: EOF`) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure platform firmware: cannot decode EV_NO_ACTION event data: cannot decode StartupLocality data: unexpected EOF`) } func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR0_2(c *C) { @@ -428,6 +491,51 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR0_2(c *C) { c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure platform firmware: log for PCR0 has an unexpected StartupLocality event`) } +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_1(c *C) { + // Insert an unexpected event type in the OS-present phase + collector := NewRootVarsCollector(efitest.NewMockHostEnvironment(nil, nil)) + ctx := newMockPcrBranchContext(&mockPcrProfileContext{ + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(BootManagerCodePCR)}, nil, collector.Next()) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d})}) + for i, event := range log.Events { + if event.PCRIndex == 4 && event.EventType == tcglog.EventTypeEFIBootServicesApplication { + log.Events[i].EventType = tcglog.EventTypeAction + break + } + } + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: unexpected OS-present event type: EV_ACTION`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_2(c *C) { + // Insert invalid event data in the OS-present phase so that internal.IsAbsoluteAgentLaunch returns an error + collector := NewRootVarsCollector(efitest.NewMockHostEnvironment(nil, nil)) + ctx := newMockPcrBranchContext(&mockPcrProfileContext{ + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(BootManagerCodePCR)}, nil, collector.Next()) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + IncludeOSPresentFirmwareAppLaunch: efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d})}) + for i, event := range log.Events { + if event.PCRIndex == 4 && event.EventType == tcglog.EventTypeEFIBootServicesApplication { + data, ok := event.Data.(*tcglog.EFIImageLoadEvent) + c.Assert(ok, testutil.IsTrue) + data.DevicePath = efi.DevicePath{} + log.Events[i].Data = data + break + } + } + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: encountered an error determining whether an OS-present launch is related to Absolute: EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path`) +} + func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogSeparatorError(c *C, pcr tpm2.Handle) error { // Insert an invalid error separator event into the log for the specified pcr collector := NewRootVarsCollector(efitest.NewMockHostEnvironment(nil, nil)) diff --git a/efi/internal/absolute.go b/efi/internal/absolute.go new file mode 100644 index 00000000..8bc8c5d5 --- /dev/null +++ b/efi/internal/absolute.go @@ -0,0 +1,86 @@ +// -*- 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 internal + +import ( + "fmt" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-efilib/guids" + "github.com/canonical/tcglog-parser" +) + +// IsAbsoluteAgentLaunch returns true if the supplied event corresponds to the launch of an +// application that is associated with the Absolute (formerly Computrace) endpoint management +// firmware. This will return false if the event is not associated with an application launch, +// or the launch is not from a firmware volume, or the launch is from a firmware volume with +// a filename that is not known to be Absolute. +// +// It will return an error if the event data is badly formed, ie, it doesn't decode properly +// to the EFI_IMAGE_LOAD_EVENT structure, there is an empty device path or a badly formed +// firmware device path that begins with a firmware volume that is not followed by a single +// firmware volume filename. +func IsAbsoluteAgentLaunch(ev *tcglog.Event) (bool, error) { + if ev.EventType != tcglog.EventTypeEFIBootServicesApplication { + // Wrong event type + return false, nil + } + data, ok := ev.Data.(*tcglog.EFIImageLoadEvent) + if !ok { + // the data resulting from decode errors is guaranteed to implement the error interface + return false, fmt.Errorf("%s event has wrong data format: %w", tcglog.EventTypeEFIBootServicesApplication, ev.Data.(error)) + } + if len(data.DevicePath) == 0 { + return false, fmt.Errorf("%s event has empty device path", tcglog.EventTypeEFIBootServicesApplication) + } + + if _, isFv := data.DevicePath[0].(efi.MediaFvDevicePathNode); !isFv { + // Not loaded from a flash volume, so this isn't Absolute + return false, nil + } + + // The image is loaded from a flash volume - we should have a path of the form "Fv()\FvFile()". + if len(data.DevicePath) != 2 { + return false, fmt.Errorf("invalid firmware volume device path (%v): invalid length (expected 2 components)", data.DevicePath) + } + + // The second component should be the filename in the firmware volume (both firmware volumes and the names + // of files inside those volumes are identified with a GUID, for which there is a public database of well + // known GUIDs). + fvf, isFvf := data.DevicePath[1].(efi.MediaFvFileDevicePathNode) + if !isFvf { + // The second component is not a firmware volume filename + return false, fmt.Errorf("invalid firmware volume device path (%v): doesn't terminate with FvFile", data.DevicePath) + } + + // We have a complete firmware volume file path. The Absolute installer application has 2 well + // known names. We can match directly by GUID or do a lookup using data in the public database. + name, known := guids.FileOrVolumeNameString(efi.GUID(fvf)) + if !known { + // This is not a well known GUID and is not Absolute. + return false, nil + } + switch name { + case "AbsoluteAbtInstaller", "AbsoluteComputraceInstaller": + return true, nil + default: + return false, nil + } +} diff --git a/efi/internal/absolute_test.go b/efi/internal/absolute_test.go new file mode 100644 index 00000000..6401cc29 --- /dev/null +++ b/efi/internal/absolute_test.go @@ -0,0 +1,221 @@ +// -*- 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 internal_test + +import ( + "fmt" + "io" + + . "gopkg.in/check.v1" + + efi "github.com/canonical/go-efilib" + "github.com/canonical/go-efilib/guids" + "github.com/canonical/tcglog-parser" + . "github.com/snapcore/secboot/efi/internal" + "github.com/snapcore/secboot/internal/testutil" +) + +type absoluteSuite struct{} + +var _ = Suite(&absoluteSuite{}) + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchTrueAbsoluteAbtInstaller(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + efi.MediaFvFileDevicePathNode(efi.MakeGUID(0x821aca26, 0x29ea, 0x4993, 0x839f, [...]byte{0x59, 0x7f, 0xc0, 0x21, 0x70, 0x8d})), + }, + }, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsTrue) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchTrueAbsoluteComputraceInstaller(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + efi.MediaFvFileDevicePathNode(efi.MakeGUID(0x8feeecf1, 0xbcfd, 0x4a78, 0x9231, [...]byte{0x48, 0x01, 0x56, 0x6b, 0x35, 0x67})), + }, + }, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsTrue) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchFalseWrongEventType(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeSeparator, + Data: &tcglog.SeparatorEventData{Value: tcglog.SeparatorEventNormalValue}, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsFalse) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchFalseNotFirmwareVolume(c *C) { + hid, err := efi.NewEISAID("PNP", 0x0a03) + c.Assert(err, IsNil) + + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + &efi.ACPIDevicePathNode{HID: hid, UID: 0}, + &efi.PCIDevicePathNode{Function: 0, Device: 6}, + &efi.PCIDevicePathNode{Function: 0, Device: 0}, + &efi.NVMENamespaceDevicePathNode{NamespaceID: 1, NamespaceUUID: 0}, + &efi.HardDriveDevicePathNode{ + PartitionNumber: 1, + PartitionStart: 0x800, + PartitionSize: 0x1000000, + Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x3b5c4f7d, 0x3934, 0x4b3f, 0xbebf, [...]byte{0x09, 0xe3, 0x53, 0xa1, 0xf7, 0x68})), + MBRType: efi.GPT, + }, + efi.NewFilePathDevicePathNode("EFI/ubuntu/shimx64.efi"), + }, + }, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsFalse) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchFalseNotWellKnownFirmwareFilename(c *C) { + // Make sure this is not a well known GUID so we know we're testing the correct thing + filename := efi.MakeGUID(0x66719632, 0xe1b3, 0x4fd8, 0x8a79, [...]byte{0x14, 0x60, 0x00, 0xf7, 0x62, 0xfc}) + _, known := guids.FileOrVolumeNameString(filename) + c.Assert(known, testutil.IsFalse) + + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + efi.MediaFvFileDevicePathNode(filename), + }, + }, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsFalse) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchFalseWrongFilename(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + efi.MediaFvFileDevicePathNode(efi.MakeGUID(0xee993080, 0x5197, 0x4d4e, 0xb63c, [...]byte{0xf1, 0xf7, 0x41, 0x3e, 0x33, 0xce})), + }, + }, + } + isAbsolute, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, IsNil) + c.Check(isAbsolute, testutil.IsFalse) +} + +type mockErrLogData struct { + err error +} + +func (d *mockErrLogData) String() string { + return fmt.Sprintf("Invalid event data: %v", d.err) +} + +func (d *mockErrLogData) Bytes() []byte { + panic("not implemented") +} + +func (d *mockErrLogData) Write(w io.Writer) error { + panic("not implemented") +} + +func (d *mockErrLogData) Error() string { + return d.err.Error() +} + +func (d *mockErrLogData) Unwrap() error { + return d.err +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchErrInvalidEventData(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &mockErrLogData{io.ErrUnexpectedEOF}, + } + _, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has wrong data format: unexpected EOF`) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchErrEmptyDevicePath(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{}, + }, + } + _, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, ErrorMatches, `EV_EFI_BOOT_SERVICES_APPLICATION event has empty device path`) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchErrWrongFvDevicePathLength(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + }, + }, + } + _, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, ErrorMatches, `invalid firmware volume device path \(\\Fv\(9375b02b-4c60-5d56-4c1c-55a699717737\)\): invalid length \(expected 2 components\)`) +} + +func (s *absoluteSuite) TestIsAbsoluteAgentLaunchErrWrongFvDevicePathTerminator(c *C) { + ev := &tcglog.Event{ + PCRIndex: 4, + EventType: tcglog.EventTypeEFIBootServicesApplication, + Data: &tcglog.EFIImageLoadEvent{ + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x9375b02b, 0x4c60, 0x5d56, 0x4c1c, [...]byte{0x55, 0xa6, 0x99, 0x71, 0x77, 0x37})), + efi.NewFilePathDevicePathNode("EFI/ubuntu/shimx64.efi"), + }, + }, + } + _, err := IsAbsoluteAgentLaunch(ev) + c.Check(err, ErrorMatches, `invalid firmware volume device path \(\\Fv\(9375b02b-4c60-5d56-4c1c-55a699717737\)\\\\EFI\\ubuntu\\shimx64.efi\): doesn't terminate with FvFile`) +} diff --git a/efi/internal/internal_test.go b/efi/internal/internal_test.go new file mode 100644 index 00000000..96dd613d --- /dev/null +++ b/efi/internal/internal_test.go @@ -0,0 +1,28 @@ +// -*- 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 internal_test + +import ( + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } diff --git a/go.mod b/go.mod index 4ef8e2dc..ad02119a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/snapcore/secboot go 1.18 require ( - github.com/canonical/go-efilib v0.9.5 + github.com/canonical/go-efilib v0.9.10-0.20240618094320-cc6dd01c07dc github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 github.com/canonical/go-tpm2 v1.3.0 diff --git a/go.sum b/go.sum index b6ab10e2..ef8a63a5 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/canonical/go-efilib v0.9.4 h1:cD6oNSWeQSgeSeJZMCxhGEW4GoLSxFhIJ12Hg3v github.com/canonical/go-efilib v0.9.4/go.mod h1:tHjv3Mni7hEpNSUNd1KJEV/AZJsFSH6LX/EQ0I75AZE= github.com/canonical/go-efilib v0.9.5 h1:zRpWG4z61GiYsEmFYvXYuj+8xV2eJ200YY5Ht9EjrRU= github.com/canonical/go-efilib v0.9.5/go.mod h1:tHjv3Mni7hEpNSUNd1KJEV/AZJsFSH6LX/EQ0I75AZE= +github.com/canonical/go-efilib v0.9.9 h1:MAVp6SOrAIsymEfUpYKOyQfu2hIm8dQ46zaG+8FMPfY= +github.com/canonical/go-efilib v0.9.9/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= +github.com/canonical/go-efilib v0.9.10-0.20240618094320-cc6dd01c07dc h1:U2xZoSBOhzYtWFJ6MhSSbIlrG8wAcqSqZhejLEimJMg= +github.com/canonical/go-efilib v0.9.10-0.20240618094320-cc6dd01c07dc/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 h1:ZE2XMRFHcwlib3uU9is37+pKkkMloVoEPWmgQ6GK1yo= github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= diff --git a/internal/efitest/log.go b/internal/efitest/log.go index 12c65a99..aa961bca 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -92,12 +92,13 @@ func (b *logBuilder) hashLogExtendEvent(c *C, data logHashData, event *logEvent) type LogOptions struct { Algorithms []tpm2.HashAlgorithmId // the digest algorithms to include - SecureBootDisabled bool - IncludeDriverLaunch bool // include a driver launch in the log - IncludeSysPrepAppLaunch bool // include a system-preparation app launch in the log - NoCallingEFIApplicationEvent bool // omit the EV_EFI_ACTION "Calling EFI Application from Boot Option" event. - NoSBAT bool // omit the SbatLevel measurement. - StartupLocality uint8 // specify a startup locality other than 0 + SecureBootDisabled bool + IncludeDriverLaunch bool // include a driver launch in the log + IncludeSysPrepAppLaunch bool // include a system-preparation app launch in the log + IncludeOSPresentFirmwareAppLaunch efi.GUID // include a flash based application launch in the log as part of the OS-present phase + NoCallingEFIApplicationEvent bool // omit the EV_EFI_ACTION "Calling EFI Application from Boot Option" event. + NoSBAT bool // omit the SbatLevel measurement. + StartupLocality uint8 // specify a startup locality other than 0 } // NewLog creates a mock TCG log for testing. The log will look like a standard @@ -251,6 +252,8 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { eventType: tcglog.EventTypeEFIBootServicesDriver, data: data}) } + + // Mock sysprep app launch if opts.IncludeSysPrepAppLaunch { if !opts.SecureBootDisabled && !opts.IncludeDriverLaunch { esd := &efi.SignatureData{ @@ -297,8 +300,6 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { data: data}) } - // Mock sysprep app launch - // Mock boot config measurements { var order [4]uint8 @@ -374,6 +375,22 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { data: data}) } + // Mock firmware application launch + var zeroGuid efi.GUID + if opts.IncludeOSPresentFirmwareAppLaunch != zeroGuid { + pe := bytesHashData(opts.IncludeOSPresentFirmwareAppLaunch[:]) + data := &tcglog.EFIImageLoadEvent{ + LocationInMemory: 0xa7b34ff7, + LengthInMemory: 56410, + DevicePath: efi.DevicePath{ + efi.MediaFvDevicePathNode(efi.MakeGUID(0x983cc241, 0xb4f6, 0x4a85, 0x9733, [...]uint8{0x4c, 0x15, 0x4b, 0x3a, 0xa3, 0x27})), + efi.MediaFvFileDevicePathNode(opts.IncludeOSPresentFirmwareAppLaunch)}} + builder.hashLogExtendEvent(c, pe, &logEvent{ + pcrIndex: 4, + eventType: tcglog.EventTypeEFIBootServicesApplication, + data: data}) + } + // Mock shim launch if !opts.SecureBootDisabled && !opts.IncludeDriverLaunch && !opts.IncludeSysPrepAppLaunch { esd := &efi.SignatureData{