Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions efi/fw_load_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
// enabled. A firmware debugger permits an adversary with local access to control
// firmware execution, bypassing any protections offered by measuredboot or verified
// boot, and the presence of one should prevent FDE from being enabled.
measuredHPPreBootDMAConfig := false
for len(events) > 0 {
e := events[0]
events = events[1:]
Expand All @@ -156,14 +157,21 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
}

if e.EventType == tcglog.EventTypeEFIVariableDriverConfig {
// This is the first secure boot configuration measurement. In most
// circumstances, this will be the first measurement to PCR7. Only
// in the case where the first event is a EV_EFI_ACTION "DMA Protection
// Disabled" event will this not be true.
// This is the first secure boot configuration measurement. It is
// generally the first measurement to PCR7, although supported
// pre-configuration action or vendor events may precede it.
break
}

switch {
case internal_efi.IsHPPreBootDMAConfigEvent(e) && !measuredHPPreBootDMAConfig:
digest := e.Digests[ctx.PCRAlg()]
expectedDigest := tcglog.ComputeStringEventDigest(ctx.PCRAlg().GetHash(), string(e.Data.Bytes()))
if !bytes.Equal(digest, expectedDigest) {
return errors.New("invalid digest for HP pre-boot DMA configuration event")
}
ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, digest)
measuredHPPreBootDMAConfig = true
case e.EventType == tcglog.EventTypeEFIAction &&
(bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabled)) || bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabledNul))) &&
allowInsufficientDMAProtection:
Expand Down Expand Up @@ -392,18 +400,49 @@ func (h *fwLoadHandler) measurePlatformFirmware(ctx pcrBranchContext) error {
}

func (h *fwLoadHandler) measureDriversAndApps(ctx pcrBranchContext) error {
seenSeparator := false

for _, event := range h.log.Events {
// Some firmware extends vendor-defined events into PCR2 after the
// separator but before authorizing or launching the initial OS loader.
// Retain those events, but do not copy any PCR2 measurements made once
// OS image processing has begun.
if seenSeparator &&
((event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
(event.PCRIndex == internal_efi.BootManagerCodePCR &&
event.EventType == tcglog.EventTypeEFIBootServicesApplication)) {
return nil
}

if event.PCRIndex != internal_efi.DriversAndAppsPCR {
continue
}

if event.EventType == tcglog.EventTypeSeparator {
return h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event)
if !seenSeparator {
if event.EventType == tcglog.EventTypeSeparator {
if err := h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event); err != nil {
return err
}
seenSeparator = true
continue
}
ctx.ExtendPCR(internal_efi.DriversAndAppsPCR, event.Digests[ctx.PCRAlg()])
continue
}

if !internal_efi.IsVendorEventType(event.EventType) {
return fmt.Errorf(
"unexpected post-separator event type %v found in PCR %d",
event.EventType, internal_efi.DriversAndAppsPCR)
}
ctx.ExtendPCR(internal_efi.DriversAndAppsPCR, event.Digests[ctx.PCRAlg()])
}

return errors.New("missing separator in log")
if !seenSeparator {
return errors.New("missing separator in log")
}
return errors.New("reached end of log before encountering initial OS authorization or launch")
}

func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error {
Expand Down
223 changes: 223 additions & 0 deletions efi/pcr2_vendor_event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
*/

package efi_test

import (
"github.com/canonical/go-tpm2"
"github.com/canonical/tcglog-parser"
. "github.com/snapcore/secboot/efi"
internal_efi "github.com/snapcore/secboot/internal/efi"
"github.com/snapcore/secboot/internal/efitest"
"github.com/snapcore/secboot/internal/testutil"
. "gopkg.in/check.v1"
)

func newLogWithPostSeparatorPCR2Event(c *C, insertedEvent *tcglog.Event) *tcglog.Log {
log := efitest.NewLog(c, &efitest.LogOptions{
Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
})

var events []*tcglog.Event
inserted := false
for _, event := range log.Events {
events = append(events, event)
if event.PCRIndex == internal_efi.PlatformManufacturerPCR &&
event.EventType == tcglog.EventTypeSeparator {
events = append(events, insertedEvent)
inserted = true
}
}
c.Assert(inserted, Equals, true)
log.Events = events
return log
}

func newLogWithPCR2EventAfterOSBoundary(c *C, opts *efitest.LogOptions, insertedEvent *tcglog.Event) *tcglog.Log {
log := efitest.NewLog(c, opts)

var events []*tcglog.Event
seenPCR2Separator := false
inserted := false
for _, event := range log.Events {
events = append(events, event)

if event.PCRIndex == internal_efi.DriversAndAppsPCR &&
event.EventType == tcglog.EventTypeSeparator {
seenPCR2Separator = true
continue
}
if !seenPCR2Separator || inserted {
continue
}

if (event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
(event.PCRIndex == internal_efi.BootManagerCodePCR &&
event.EventType == tcglog.EventTypeEFIBootServicesApplication) {
events = append(events, insertedEvent)
inserted = true
}
}
c.Assert(inserted, Equals, true)
log.Events = events
return log
}

// TestMeasureImageStartDriversAndAppsIncludesPostSeparatorVendorEvent
// reproduces the event ordering from the HP ZBook Ultra G1a firmware:
//
// PCR2 EV_SEPARATOR
// ...
// PCR2 vendor event 0x00008401 with data 0x01
// ...
// PCR7 EV_EFI_VARIABLE_AUTHORITY
// PCR4 EV_EFI_BOOT_SERVICES_APPLICATION
//
// The event digest must be retained in the generated PCR2 profile because the
// firmware extends it into the TPM before launching the OS.
func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsIncludesPostSeparatorVendorEvent(c *C) {
vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: 0x00008401,
Digests: tcglog.DigestMap{
tpm2.HashAlgorithmSHA256: vendorDigest,
},
Data: tcglog.OpaqueEventData{0x01},
})

s.testMeasureImageStart(c, &testFwMeasureImageStartData{
log: log,
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
expectedEvents: []*mockPcrBranchEvent{
{pcr: 2, eventType: mockPcrBranchResetEvent},
{pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
{pcr: 2, eventType: mockPcrBranchExtendEvent, digest: vendorDigest},
},
})
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsPostSeparatorStandardEvent(c *C) {
log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: tcglog.EventTypeEFIAction,
Digests: tcglog.DigestMap{
tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
},
Data: tcglog.OpaqueEventData{0x01},
})

collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
ctx := newMockPcrBranchContext(&mockPcrProfileContext{
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
}, nil, collector.Next())

handler := NewFwLoadHandler(log)
c.Check(
handler.MeasureImageStart(ctx),
ErrorMatches,
`cannot measure drivers and apps: unexpected post-separator event type EV_EFI_ACTION found in PCR 2`)
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsMissingOSBoundary(c *C) {
log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: 0x00008401,
Digests: tcglog.DigestMap{
tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
},
Data: tcglog.OpaqueEventData{0x01},
})

truncated := false
for i, event := range log.Events {
if (event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
(event.PCRIndex == internal_efi.BootManagerCodePCR &&
event.EventType == tcglog.EventTypeEFIBootServicesApplication) {
log.Events = log.Events[:i]
truncated = true
break
}
}
c.Assert(truncated, Equals, true)

collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
ctx := newMockPcrBranchContext(&mockPcrProfileContext{
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
}, nil, collector.Next())

handler := NewFwLoadHandler(log)
c.Check(
handler.MeasureImageStart(ctx),
ErrorMatches,
`cannot measure drivers and apps: reached end of log before encountering initial OS authorization or launch`)
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsStopsAtVariableAuthorityBoundary(c *C) {
vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
log := newLogWithPCR2EventAfterOSBoundary(c, &efitest.LogOptions{
Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
}, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: 0x00008401,
Digests: tcglog.DigestMap{
tpm2.HashAlgorithmSHA256: vendorDigest,
},
Data: tcglog.OpaqueEventData{0x01},
})

s.testMeasureImageStart(c, &testFwMeasureImageStartData{
log: log,
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
expectedEvents: []*mockPcrBranchEvent{
{pcr: 2, eventType: mockPcrBranchResetEvent},
{pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
},
})
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsStopsAtImageLaunchBoundary(c *C) {
vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
log := newLogWithPCR2EventAfterOSBoundary(c, &efitest.LogOptions{
Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
SecureBootDisabled: true,
}, &tcglog.Event{
PCRIndex: internal_efi.DriversAndAppsPCR,
EventType: 0x00008401,
Digests: tcglog.DigestMap{
tpm2.HashAlgorithmSHA256: vendorDigest,
},
Data: tcglog.OpaqueEventData{0x01},
})

s.testMeasureImageStart(c, &testFwMeasureImageStartData{
log: log,
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
expectedEvents: []*mockPcrBranchEvent{
{pcr: 2, eventType: mockPcrBranchResetEvent},
{pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
},
})
}
Loading