From c028e6b8fcab55520e04ad12d008d55063cdc84d Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 22 May 2024 18:28:18 +0100 Subject: [PATCH 1/2] efi: PCR4: Add support for certain firmware applications loaded during OS-present Some newer laptops are loading endpoint management applications that are shipped with firmware, and these are being loaded as part of the OS-present environment with the LoadImage API, resulting in them being measured to PCR4. This currently causes us to mis-predict values for PCR4 because everything after the separator (the pre-OS to OS-present transition) is assumed to belong to the OS and gets dropped from the predicted measurements. A future PR will provide support for detecting compatibility for FDE, with an option for detecting and disallowing these firmware applications. In most cases, they should be disabled (and it's possible to disable Absolute from userspace on Dell laptops), but we should add support here for specific, known endpoint management applications for any edge cases where these agents cannot be disabled. This adds support for any applications where the firmware volume filename GUID is well known and maps to "AbsoluteAbtInstaller" or "AbsoluteComputraceInstaller". --- efi/fw_load_handler.go | 83 ++++++++++++++-- efi/fw_load_handler_test.go | 190 +++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 2 + internal/efitest/log.go | 33 +++++-- 5 files changed, 292 insertions(+), 18 deletions(-) diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index 2930e391..9790a3a0 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -26,6 +26,7 @@ import ( "fmt" efi "github.com/canonical/go-efilib" + "github.com/canonical/go-efilib/guids" "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" "golang.org/x/xerrors" @@ -236,22 +237,90 @@ func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error // 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 { + // 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, else return an error. Anything else here will be picked up by the pre-install + // checks. + 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) + } + + data, ok := event.Data.(*tcglog.EFIImageLoadEvent) + if !ok { + return fmt.Errorf("invalid event data for OS-present event: %w", event.Data.(error)) + } + + if len(data.DevicePath) == 0 { + return errors.New("invalid device path for first OS-present image load event: device path is empty") + } + if _, isFv := data.DevicePath[0].(efi.MediaFvDevicePathNode); !isFv { + // Not loaded from flash, so we'll break here, assuming that this is the first OS component + break + } + + // The image is loaded from flash - we should have a path of the form "Fv()\FvFile()". + if len(data.DevicePath) != 2 { + return fmt.Errorf("invalid firmware volume device path (%v) for OS-present image load: invalid length", data.DevicePath) + } + fvf, isFvf := data.DevicePath[1].(efi.MediaFvFileDevicePathNode) + if !isFvf { + // The second component should be the firmware volume file name. + return fmt.Errorf("invalid firmware volume device path (%v) for OS-present image load: doesn't terminate with FvFile", data.DevicePath) + } + + name, known := guids.FileNameString(efi.GUID(fvf)) + if !known { + return fmt.Errorf("unknown image loaded from firmware volume during OS-present: %v", data.DevicePath) + } + switch name { + case "AbsoluteAbtInstaller", "AbsoluteComputraceInstaller": + ctx.ExtendPCR(bootManagerCodePCR, tpm2.Digest(event.Digests[ctx.PCRAlg()])) + default: + return fmt.Errorf("unexpected image loaded from firmware volume during OS-present: %v", data.DevicePath) + } + if known { + // We've copied the measurement for "AbsoluteAbtInstaller" or "AbsoluteComputraceInstaller", + // so we're finished here. + 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..aa3a6178 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -192,6 +192,48 @@ 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) TestMeasureImageStartSecureBootPolicyAndBootManagerCodeProfile(c *C) { vars := makeMockVars(c, withMsSecureBootConfig()) s.testMeasureImageStart(c, &testFwMeasureImageStartData{ @@ -390,13 +432,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 +470,150 @@ 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 + 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].Data = &mockErrLogData{io.ErrUnexpectedEOF} + break + } + } + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid event data for OS-present event: unexpected EOF`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_3(c *C) { + // Delete the device path for the first OS-present application launch + 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: invalid device path for first OS-present image load event: device path is empty`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_4(c *C) { + // Delete the FvFile component of the device path for the first OS-present application launch + 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 = data.DevicePath[:1] + log.Events[i].Data = data + break + } + } + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid firmware volume device path \(\\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\) for OS-present image load: invalid length`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_5(c *C) { + // Replace the FvFile component of the device path for the first OS-present application launch with something else + 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[1] = efi.FilePathDevicePathNode("/foo") + log.Events[i].Data = data + break + } + } + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid firmware volume device path \(\\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\\/foo\) for OS-present image load: doesn't terminate with FvFile`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_6(c *C) { + // Have an unknown component load from firmware as part of the OS-present environment + 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(0xa52f647a, 0x2554, 0x4721, 0x80e5, [...]byte{0x61, 0x7e, 0x4c, 0x50, 0xf2, 0xd8})}) + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: unknown image loaded from firmware volume during OS-present: \\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\FvFile\(a52f647a-2554-4721-80e5-617e4c50f2d8\)`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_7(c *C) { + // Have an unexpected component load from firmware as part of the OS-present environment + 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(0x8218965d, 0x20c0, 0x4dd6, 0x81a0, [...]byte{0x84, 0x5c, 0x52, 0x27, 0x07, 0x43})}) + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: unexpected image loaded from firmware volume during OS-present: \\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\FvFile\(LenovoSetupDateTimeDxe\)`) +} + 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/go.mod b/go.mod index 4ef8e2dc..92dc773f 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.9 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..b1c568be 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ 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-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{ From e51f4c615c0965943cf5f6dc2ac81fb52b173aaf Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 19 Jun 2024 21:18:24 +0100 Subject: [PATCH 2/2] efi: introduce internal package and move Absolute detection there The same code will be shared by the pre-install checks which are going to go into a new package (efi/preinstall), so create an internal package for removing some interdependencies and move the Absolute detection logic there instead --- efi/fw_load_handler.go | 67 ++++------- efi/fw_load_handler_test.go | 124 ++++--------------- efi/internal/absolute.go | 86 +++++++++++++ efi/internal/absolute_test.go | 221 ++++++++++++++++++++++++++++++++++ efi/internal/internal_test.go | 28 +++++ go.mod | 2 +- go.sum | 2 + 7 files changed, 386 insertions(+), 144 deletions(-) create mode 100644 efi/internal/absolute.go create mode 100644 efi/internal/absolute_test.go create mode 100644 efi/internal/internal_test.go diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index 9790a3a0..a43f593d 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -26,9 +26,9 @@ import ( "fmt" efi "github.com/canonical/go-efilib" - "github.com/canonical/go-efilib/guids" "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" + "github.com/snapcore/secboot/efi/internal" "golang.org/x/xerrors" ) @@ -228,17 +228,23 @@ 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. The UEFI specification says that system - // preparation applications are executed before the ready to boot signal, which is when the transition + // 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 @@ -267,8 +273,8 @@ func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error // 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, else return an error. Anything else here will be picked up by the pre-install - // checks. + // 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:] @@ -280,44 +286,21 @@ func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error return fmt.Errorf("unexpected OS-present event type: %v", event.EventType) } - data, ok := event.Data.(*tcglog.EFIImageLoadEvent) - if !ok { - return fmt.Errorf("invalid event data for OS-present event: %w", event.Data.(error)) - } - - if len(data.DevicePath) == 0 { - return errors.New("invalid device path for first OS-present image load event: device path is empty") - } - if _, isFv := data.DevicePath[0].(efi.MediaFvDevicePathNode); !isFv { - // Not loaded from flash, so we'll break here, assuming that this is the first OS component - break - } - - // The image is loaded from flash - we should have a path of the form "Fv()\FvFile()". - if len(data.DevicePath) != 2 { - return fmt.Errorf("invalid firmware volume device path (%v) for OS-present image load: invalid length", data.DevicePath) - } - fvf, isFvf := data.DevicePath[1].(efi.MediaFvFileDevicePathNode) - if !isFvf { - // The second component should be the firmware volume file name. - return fmt.Errorf("invalid firmware volume device path (%v) for OS-present image load: doesn't terminate with FvFile", data.DevicePath) - } + // once we encounter the first EV_EFI_BOOT_SERVICES_APPLICATION event in PCR4, this loop alway + // breaks or returns an error. - name, known := guids.FileNameString(efi.GUID(fvf)) - if !known { - return fmt.Errorf("unknown image loaded from firmware volume during OS-present: %v", data.DevicePath) + 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) } - switch name { - case "AbsoluteAbtInstaller", "AbsoluteComputraceInstaller": + if isAbsolute { + // copy the digest to the policy ctx.ExtendPCR(bootManagerCodePCR, tpm2.Digest(event.Digests[ctx.PCRAlg()])) - default: - return fmt.Errorf("unexpected image loaded from firmware volume during OS-present: %v", data.DevicePath) - } - if known { - // We've copied the measurement for "AbsoluteAbtInstaller" or "AbsoluteComputraceInstaller", - // so we're finished here. - break } + // 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 diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index aa3a6178..5869e5bb 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -234,6 +234,27 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartBootManagerCodeProfileIncludeA }) } +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{ @@ -492,28 +513,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_1(c *C) { } func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_2(c *C) { - // Insert invalid event data 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].Data = &mockErrLogData{io.ErrUnexpectedEOF} - break - } - } - - handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid event data for OS-present event: unexpected EOF`) -} - -func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_3(c *C) { - // Delete the device path for the first OS-present application launch + // 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, @@ -533,85 +533,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_3(c *C) { } handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid device path for first OS-present image load event: device path is empty`) -} - -func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_4(c *C) { - // Delete the FvFile component of the device path for the first OS-present application launch - 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 = data.DevicePath[:1] - log.Events[i].Data = data - break - } - } - - handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid firmware volume device path \(\\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\) for OS-present image load: invalid length`) -} - -func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_5(c *C) { - // Replace the FvFile component of the device path for the first OS-present application launch with something else - 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[1] = efi.FilePathDevicePathNode("/foo") - log.Events[i].Data = data - break - } - } - - handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: invalid firmware volume device path \(\\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\\/foo\) for OS-present image load: doesn't terminate with FvFile`) -} - -func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_6(c *C) { - // Have an unknown component load from firmware as part of the OS-present environment - 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(0xa52f647a, 0x2554, 0x4721, 0x80e5, [...]byte{0x61, 0x7e, 0x4c, 0x50, 0xf2, 0xd8})}) - - handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: unknown image loaded from firmware volume during OS-present: \\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\FvFile\(a52f647a-2554-4721-80e5-617e4c50f2d8\)`) -} - -func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_7(c *C) { - // Have an unexpected component load from firmware as part of the OS-present environment - 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(0x8218965d, 0x20c0, 0x4dd6, 0x81a0, [...]byte{0x84, 0x5c, 0x52, 0x27, 0x07, 0x43})}) - - handler := NewFwLoadHandler(log) - c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure boot manager code: unexpected image loaded from firmware volume during OS-present: \\Fv\(983cc241-b4f6-4a85-9733-4c154b3aa327\)\\FvFile\(LenovoSetupDateTimeDxe\)`) + 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 { 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 92dc773f..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.9 + 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 b1c568be..ef8a63a5 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/canonical/go-efilib v0.9.5 h1:zRpWG4z61GiYsEmFYvXYuj+8xV2eJ200YY5Ht9E 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=