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
40 changes: 22 additions & 18 deletions efi/fw_load_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,28 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error

// TODO: Support optional dbt/dbr database

// Include the user mode related measurements if the system is in user mode, it is
// permitted with the WithSecureBootUserMode option and they are being included in this
// branch.
includeUserMode := boolParamOrFalse(ctx.Params(), includeSecureBootUserModeParamKey)
switch deployedMode, _, err := ctx.Vars().ReadVar("DeployedMode", efi.GlobalVariable); {
case errors.Is(err, efi.ErrVarNotExist):
// pre-2.5 UEFI system
case err != nil:
return fmt.Errorf("cannot read DeployedMode variable: %w", err)
case len(deployedMode) != 1:
return fmt.Errorf("invalid DeployedMode value %#x", deployedMode)
case deployedMode[0] == 0 && includeUserMode:
// System is in user mode, the WithSecureBootUserMode option was supplied and
// we are including the user mode related measurements in this branch.
ctx.MeasureVariable(internal_efi.SecureBootPolicyPCR, efi.GlobalVariable, "AuditMode", []byte{0})
ctx.MeasureVariable(internal_efi.SecureBootPolicyPCR, efi.GlobalVariable, "DeployedMode", []byte{0})
default:
// Do nothing for the deployed mode case, or where the system is in user mode
// but where the WithSecureBootUserMode option is not supplied or we are creating
// a branch that allows for deployed mode to be enabled.
}

// We don't measure a EV_SEPARATOR here yet because we need to preserve the
// device-specific measurement ordering - see the notes above about when the
// verification of third-party pre-OS code is measured. We don't know whether
Expand Down Expand Up @@ -256,24 +278,6 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
// once we've encountered the first EV_EFI_VARIABLE_AUTHORITY event and
// we'll likely generate an invalid profile if we do. The preinstall
// checks will catch this.

// Except...
// Backward compliance: On Ubuntu Core not using preinstall checks,
// the firmware might be UEFI 2.5 compliant but not be in deployed mode.
// In that case we should still expect those measurements due to the mode.
if data, ok := e.Data.(*tcglog.EFIVariableData); ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we need to use WithSecureBootUserMode for pre UC26 core systems?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should always use it also on UC26. I expect a lot of customers will not be able to update to 26.04 because a lot of hardware is not yet ready. And maybe plan it for UC28.

Copy link
Collaborator

@valentindavid valentindavid Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that Hybrid will allow users to override this. But Core will not be able to do that, so it has to be automatic.
As long as we make Hybrid users able to ignore this, we should also make Core automatically accept it.

if (data.VariableName == efi.GlobalVariable && data.UnicodeName == "AuditMode") {
if bytes.Equal(data.VariableData, []byte{0}) {
ctx.MeasureVariable(internal_efi.SecureBootPolicyPCR, efi.GlobalVariable, "AuditMode", data.VariableData)
}
}
if (data.VariableName == efi.GlobalVariable && data.UnicodeName == "DeployedMode") {
if bytes.Equal(data.VariableData, []byte{0}) {
ctx.MeasureVariable(internal_efi.SecureBootPolicyPCR, efi.GlobalVariable, "DeployedMode", data.VariableData)
}
}
}

case e.PCRIndex == internal_efi.SecureBootPolicyPCR && e.EventType == tcglog.EventTypeEFIAction &&
(bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabled)) || bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabledNul))) &&
allowInsufficientDMAProtection:
Expand Down
27 changes: 26 additions & 1 deletion efi/fw_load_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageLoadSecureBootPolicyAndBootManagerC
})
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileDisabledDeployedMode(c *C) {
func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileUserMode(c *C) {
vars := makeMockVars(c, withMsSecureBootConfig(), withDeployedModeDisabled())
s.testMeasureImageStart(c, &testFwMeasureImageStartData{
vars: vars,
Expand All @@ -1206,6 +1206,9 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileDisable
},
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR),
loadParams: &LoadParams{
"include_secure_boot_user_mode": true,
},
expectedEvents: []*mockPcrBranchEvent{
{pcr: 7, eventType: mockPcrBranchResetEvent},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: efi.VariableDescriptor{Name: "SecureBoot", GUID: efi.GlobalVariable}, varData: []byte{0x01}},
Expand All @@ -1219,3 +1222,25 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileDisable
},
})
}

func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileUserModeNotIncluded(c *C) {
vars := makeMockVars(c, withMsSecureBootConfig(), withDeployedModeDisabled())
s.testMeasureImageStart(c, &testFwMeasureImageStartData{
vars: vars,
logOptions: &efitest.LogOptions{
Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1},
DisableDeployedMode: true,
},
alg: tpm2.HashAlgorithmSHA256,
pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR),
expectedEvents: []*mockPcrBranchEvent{
{pcr: 7, eventType: mockPcrBranchResetEvent},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: efi.VariableDescriptor{Name: "SecureBoot", GUID: efi.GlobalVariable}, varData: []byte{0x01}},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: PK, varData: vars[PK].Payload},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: KEK, varData: vars[KEK].Payload},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: Db, varData: vars[Db].Payload},
{pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: Dbx, varData: vars[Dbx].Payload},
{pcr: 7, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, // EV_SEPARATOR
},
})
}
64 changes: 64 additions & 0 deletions efi/pcr_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,3 +1377,67 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithDbxUpdateWithAllowInsufficien
}, WithSecureBootPolicyProfile(), WithBootManagerCodeProfile(), WithKernelConfigProfile(), WithSignatureDBUpdates(&SignatureDBUpdate{Name: Dbx, Data: msDbxUpdate2}), WithAllowInsufficientDmaProtection())
c.Check(err, IsNil)
}

func (s *pcrProfileSuite) TestAddPCRProfileUC20WithAllowSecureBootUserMode(c *C) {
shim := newMockUbuntuShimImage15_7(c)
grub := newMockUbuntuGrubImage3(c)
recoverKernel := newMockUbuntuKernelImage2(c)
runKernel := newMockUbuntuKernelImage3(c)

err := s.testAddPCRProfile(c, &testAddPCRProfileData{
vars: makeMockVars(c, withMsSecureBootConfig(), withSbatLevel([]byte("sbat,1,2022052400\ngrub,2\n")), withDeployedModeDisabled()),
log: efitest.NewLog(c, &efitest.LogOptions{
Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1},
}),
alg: tpm2.HashAlgorithmSHA256,
loadSequences: NewImageLoadSequences(
SnapModelParams(testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{
"authority-id": "fake-brand",
"series": "16",
"brand-id": "fake-brand",
"model": "fake-model",
"grade": "secured",
}, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")),
).Append(
NewImageLoadActivity(shim).Loads(
NewImageLoadActivity(grub, KernelCommandlineParams("console=ttyS0 console=tty1 panic=-1 systemd.gpt_auto=0 snapd_recovery_mode=recover")).Loads(
NewImageLoadActivity(grub, KernelCommandlineParams("console=ttyS0 console=tty1 panic=-1 systemd.gpt_auto=0 snapd_recovery_mode=run")).Loads(
NewImageLoadActivity(runKernel),
),
NewImageLoadActivity(recoverKernel),
),
),
),
expected: []tpm2.PCRValues{
{
tpm2.HashAlgorithmSHA256: {
4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"),
7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"),
12: testutil.DecodeHexString(c, "fd1000c6f691c3054e2ff5cfacb39305820c9f3534ba67d7894cb753aa85074b"),
},
},
{
tpm2.HashAlgorithmSHA256: {
4: testutil.DecodeHexString(c, "c731a39b7fc6475c7d8a9264e704902157c7cee40c22f59fa1690ea99ff70c67"),
7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"),
12: testutil.DecodeHexString(c, "5b354c57a61bb9f71fcf596d7e9ef9e2e0d6f4ad8151c9f358e6f0aaa7823756"),
},
},
{
tpm2.HashAlgorithmSHA256: {
4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"),
7: testutil.DecodeHexString(c, "d6a99cb0ea5ac9290b65b8847bb48613fc504a798229fb37e88b7b33337f4c60"),
12: testutil.DecodeHexString(c, "fd1000c6f691c3054e2ff5cfacb39305820c9f3534ba67d7894cb753aa85074b"),
},
},
{
tpm2.HashAlgorithmSHA256: {
4: testutil.DecodeHexString(c, "c731a39b7fc6475c7d8a9264e704902157c7cee40c22f59fa1690ea99ff70c67"),
7: testutil.DecodeHexString(c, "d6a99cb0ea5ac9290b65b8847bb48613fc504a798229fb37e88b7b33337f4c60"),
12: testutil.DecodeHexString(c, "5b354c57a61bb9f71fcf596d7e9ef9e2e0d6f4ad8151c9f358e6f0aaa7823756"),
},
},
},
}, WithSecureBootPolicyProfile(), WithBootManagerCodeProfile(), WithKernelConfigProfile(), WithAllowSecureBootUserMode())
c.Check(err, IsNil)
}
33 changes: 14 additions & 19 deletions efi/preinstall/check_pcr7.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ type secureBootPolicyResultFlags int
const (
secureBootIncludesWeakAlg secureBootPolicyResultFlags = 1 << iota // Weak algorithms were used during image verification.
secureBootPreOSVerificationIncludesDigest // Authenticode digests were used to authenticate pre-OS components.
secureBootNoDeployedMode // Deployed mode is not enabled.
)

// secureBootPolicyResult is the result of a successful call to checkSecureBootPolicyMeasurementsAndObtainAuthorities.
Expand All @@ -300,49 +301,49 @@ type secureBootPolicyResult struct {
}

// checkSecureBootPolicyMeasurementsAndObtainAuthorities performs some checks on the secure boot policy PCR (7).

//
// The supplied context is used to attach an EFI variable backend to, for functions that read
// from EFI variables. The supplied env and log arguments provide other inputs to this function.
// The pcrAlg argument is the PCR bank that is chosen as the best one to use. The iblImage
// corresponds to the initial boot loader image for the current boot. This is used to detect the
// launch of the OS, at which checks for PCR7 end. There are some limitations of this, ie, we may
// not detect LoadImage bugs that happen later on, but once the OS has loaded, it's impossible to
// tell whicj events come from firmware and which are under the control of OS components.

//
// This ensures that secure boot is enabled, else an error is returned, as WithSecureBootPolicyProfile
// only generates profiles compatible with secure boot being enabled.

//
// If the version of UEFI is >= 2.5, it also makes sure that the secure boot mode is "deployed mode".
// If the secure boot mode is "user mode", then the "AuditMode" and "DeployedMode" values are measured to PCR7,
// something that WithSecureBootPolicyProfile doesn't support today. Support for "user mode" will be added
// in the future, although the public RunChecks API will probably require a flag to opt in to supporting user
// mode, as it is the less secure mode of the 2 (see the documentation for SecureBootMode in
// github.com/canonical/go-efilib).

//
// It also reads the "OsIndicationsSupported" variable to test for features that are not supported by
// WithSecureBootPolicyProfile. These are timestamp revocation (which requires an extra signature database -
// "dbt") and OS recovery (which requires an extra signature database -"dbr", used to control access to
// OsRecoveryOrder and OsRecover#### variables). Of the 2, it's likely that we might need to add support for
// timestamp revocation at some point in the future.

//
// It reads the "BootCurrent" EFI variable and matches this to the EFI_LOAD_OPTION associated with the current
// boot from the TCG log - it uses the log as "BootXXXX" EFI variables can be updated at runtime and
// might be out of data when this code runs. It uses this to detect the launch of the initial boot loader,
// which might not necessarily be the first EV_EFI_BOOT_SERVICES_APPLICATION event in the OS-present
// environment in PCR4 (eg, if Absolute is active).

//
// After these checks, it iterates over the secure boot configuration in the log, making sure that the
// configuration is measured in the correct order, that the event data is valid, and that the measured digest
// is the tagged hash of the event data. It makes sure that the value of "SecureBoot" in the log is consistent
// with the "SecureBoot" variable (which is read-only at runtime), and it verifies that all of the signature
// databases are formatted correctly and can be decoded. It will return an error if any of these checks fail.

//
// If the pre-OS environment contains events other than EV_EFI_VARIABLE_DRIVER_CONFIG, it will return an error.
// This can happen a firmware debugger is enabled, in which case PCR7 will begin with a EV_EFI_ACTION
// "UEFI Debug Mode" event. This case is detected by earlier firmware protection checks.

//
// If not all of the expected secure boot configuration is measured, an error is returned.

//
// Once the secure boot configuration has been measured, it looks for EV_EFI_VARIABLE_AUTHORITY events in PCR7,
// until it detects the launch of the initial boot loader. It verifies that each of these come from db, and
// if the log is in the OS-present environment, it ensures that the measured digest is the tagged hash of the
Expand All @@ -357,7 +358,7 @@ type secureBootPolicyResult struct {
// reflect the new components each time. If the digest being matched is SHA-1, it sets the flag in the return
// value indicating a weak algorithm. If any of these checks fail, an error is returned. If an event type
// other than EV_EFI_VARIABLE_AUTHORITY is detected, an error is returned.

//
// Upon detecting the launch of the initial boot loader in PCR4, it extracts the authenticode signatures from
// the supplied image, and matches these to a previously measured CA. If no match is found, an error is returned.
// If a match is found, it ensures that the signing certificate has an RSA public key with a modulus that is at
Expand Down Expand Up @@ -389,23 +390,18 @@ func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context,
return nil, ErrNoSecureBoot
}

result = new(secureBootPolicyResult)

// On UEFI 2.5 and later, we require that deployed mode is enabled, because if it's disabled, it
// changes the sequence of events for PCR7 (the DeployedMode and AuditMode global variables are
// also measured).
// TODO(chrisccoulson): relax this later on in the profile generation to support user mode, but
// maybe add a new flag (RequireDeployedMode or AllowUserMode) to RunChecks. We should be
// able to generate policies for user mode as well - it shouldn't be necessary to enable deployed
// mode as long as secure boot is enabled, particularly because the only paths back from deployed
// mode are platform specific (ie, it could be a one way operation!)
if efi.IsDeployedModeSupported(varCtx) {
secureBootMode, err := efi.ComputeSecureBootMode(varCtx)
if err != nil {
return nil, fmt.Errorf("cannot compute secure boot mode: %w", err)
}
if secureBootMode != efi.DeployedMode {
// WithSecureBootPolicyProfile() doesn't generate working profiles if deployed mode is not
// enabled on UEFI >= 2.5.
return nil, ErrNoDeployedMode
result.Flags |= secureBootNoDeployedMode
}
}

Expand Down Expand Up @@ -449,7 +445,6 @@ func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context,
// TODO: Add optional dbt / SPDM in the future.
}

result = new(secureBootPolicyResult)
var (
db efi.SignatureDatabase // The authorized signature database from the TCG log.
measuredSignatures tpm2.DigestList // The verification event digests measured by the firmware
Expand Down
Loading
Loading