From e25b8214ea08f7784e792786da37631dee06ea34 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 12 Feb 2026 21:55:11 +0000 Subject: [PATCH] efi, efi/preinstall: Allow opt-in to secure boot being in user mode The preinstall checks currently require a system to be in deployed mode (for UEFI versions >= 2.5). Relax this to allow an opt-in to user mode for systems that run UEFI versions >= 2.5 but where the firmware settings don't permit enabling deployed mode. To support this, a new WithSecureBootUserMode option is added for AddPCRProfile. If this option is supplied on a system that is in user mode, 2 branches will be created. One of these will include the user mode related measurements and the other branch will be for deployed mode, which allows a system to transition from user mode to deployed mode without requiring a recovery key. This is an opt-in rather than automatic to avoid the scenario where a system is initially in deployed mode but a later firmware configuration change reverts to user mode, the user enters their recovery key on the next boot and then snapd automatically repairs with a PCR profile that includes the newly degraded setting. In this case, we want the user to explicitly opt-in to this as an acknowledgement that the firmware configuration has been changed. Fixes: FR-12184 Fixes: https://github.com/canonical/secboot/issues/502 --- efi/fw_load_handler.go | 40 ++- efi/fw_load_handler_test.go | 27 +- efi/pcr_profile_test.go | 64 ++++ efi/preinstall/check_pcr7.go | 33 +- efi/preinstall/check_pcr7_test.go | 30 +- efi/preinstall/checks.go | 12 + efi/preinstall/checks_context.go | 84 +++-- efi/preinstall/checks_context_test.go | 460 +++++++++++++++----------- efi/preinstall/checks_test.go | 115 ++++++- efi/preinstall/error_kinds.go | 73 ++++ efi/preinstall/errors.go | 6 +- efi/preinstall/export_test.go | 1 + efi/preinstall/profile.go | 5 + efi/preinstall/profile_test.go | 24 ++ efi/secureboot.go | 40 +++ 15 files changed, 721 insertions(+), 293 deletions(-) diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index fbd9d720..f5ef5ff9 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -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 @@ -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 { - 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: diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index 9f68cec5..02515a94 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -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, @@ -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}}, @@ -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 + }, + }) +} diff --git a/efi/pcr_profile_test.go b/efi/pcr_profile_test.go index e6c5f970..b6848f28 100644 --- a/efi/pcr_profile_test.go +++ b/efi/pcr_profile_test.go @@ -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) +} diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go index 8d64e7fc..586fb1e0 100644 --- a/efi/preinstall/check_pcr7.go +++ b/efi/preinstall/check_pcr7.go @@ -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. @@ -300,7 +301,7 @@ 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 @@ -308,41 +309,41 @@ type secureBootPolicyResult struct { // 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 @@ -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 @@ -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 } } @@ -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 diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go index c2f8d83b..a5235653 100644 --- a/efi/preinstall/check_pcr7_test.go +++ b/efi/preinstall/check_pcr7_test.go @@ -391,21 +391,18 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesWit c.Check(err, IsNil) } -func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadNoSecureBoot(c *C) { +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodUserMode(c *C) { err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ env: efitest.NewMockHostEnvironmentWithOpts( efitest.WithMockVars(efitest.MockVars{ {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, - {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), - efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ - Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - SecureBootDisabled: true, - })), + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), ), pcrAlg: tpm2.HashAlgorithmSHA256, iblImage: &mockImage{ @@ -413,22 +410,29 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), }, }, + expectedFlags: SecureBootNoDeployedMode, + expectedUsedAuthorities: []*X509CertificateID{ + NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)), + }, }) - c.Check(err, Equals, ErrNoSecureBoot) + c.Check(err, IsNil) } -func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadUserMode(c *C) { +func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadNoSecureBoot(c *C) { err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{ env: efitest.NewMockHostEnvironmentWithOpts( efitest.WithMockVars(efitest.MockVars{ {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, - {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), - efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + SecureBootDisabled: true, + })), ), pcrAlg: tpm2.HashAlgorithmSHA256, iblImage: &mockImage{ @@ -437,7 +441,7 @@ func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBad }, }, }) - c.Check(err, Equals, ErrNoDeployedMode) + c.Check(err, Equals, ErrNoSecureBoot) } func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesBadFWSupportsDBT(c *C) { diff --git a/efi/preinstall/checks.go b/efi/preinstall/checks.go index 8a22e70a..9e11f956 100644 --- a/efi/preinstall/checks.go +++ b/efi/preinstall/checks.go @@ -143,6 +143,10 @@ const ( // and/or if no kernel IOMMU support was detected. // This weakens security because it allows DMA attacks to compromise system integrity. PermitInsufficientDMAProtection + + // PermitSecureBootUserMode will prevent RunChecks from returning an error if secure + // boot is enabled but not in deployed mode on systems that support UEFI >= 2.5. + PermitSecureBootUserMode ) var ( @@ -432,6 +436,9 @@ func RunChecks(ctx context.Context, flags CheckFlags, loadedImages []secboot_efi if pcr7Result.Flags&secureBootPreOSVerificationIncludesDigest > 0 { addDeferredErrorOrWarning(ErrPreOSSecureBootAuthByEnrolledDigests, PermitPreOSSecureBootAuthByEnrolledDigests) } + if pcr7Result.Flags&secureBootNoDeployedMode > 0 { + addDeferredErrorOrWarning(ErrNoDeployedMode, PermitSecureBootUserMode) + } result.UsedSecureBootCAs = pcr7Result.UsedAuthorities } @@ -444,6 +451,11 @@ func RunChecks(ctx context.Context, flags CheckFlags, loadedImages []secboot_efi result.AcceptedErrors[kind] = nil } } + for info, flag := range errorKindWithArgsToProceedFlag { + if flag&flags > 0 { + result.AcceptedErrors[info.kind] = nil + } + } if len(warnings) > 0 { result.Warnings = joinErrors(warnings...).(CompoundError) } diff --git a/efi/preinstall/checks_context.go b/efi/preinstall/checks_context.go index 4bf57fa8..e753078b 100644 --- a/efi/preinstall/checks_context.go +++ b/efi/preinstall/checks_context.go @@ -26,6 +26,7 @@ import ( "fmt" "time" + efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/ppi" secboot_efi "github.com/snapcore/secboot/efi" @@ -61,6 +62,13 @@ var errorKindToActions map[ErrorKind][]Action // to ignore the error. Not all errors can be ignored in this way. var errorKindToProceedFlag map[ErrorKind]CheckFlags +type errorKindWithArgs struct { + kind ErrorKind + args any +} + +var errorKindWithArgsToProceedFlag map[errorKindWithArgs]CheckFlags + // unsupportedPcrs are the PCRs that are currently unsupported. var unsupportedPcrs tpm2.HandleList @@ -159,6 +167,10 @@ func init() { ErrorKindPreOSSecureBootAuthByEnrolledDigests: PermitPreOSSecureBootAuthByEnrolledDigests, } + errorKindWithArgsToProceedFlag = map[errorKindWithArgs]CheckFlags{ + {kind: ErrorKindInvalidSecureBootMode, args: SecureBootModeArg{Enabled: true, Mode: efi.UserMode}}: PermitSecureBootUserMode, + } + unsupportedPcrs = tpm2.HandleList{ internal_efi.PlatformConfigPCR, internal_efi.DriversAndAppsConfigPCR, @@ -198,7 +210,7 @@ type RunChecksContext struct { // proceedFlags indicates the CheckFlags that can be enabled if Run is called // with ActionProceed. - proceedFlags CheckFlags + proceedFlags map[ErrorKind]CheckFlags } // NewRunChecksContext returns a new RunChecksContext instance with the initial flags for [RunChecks] @@ -376,7 +388,7 @@ func insertActionProceed(actions []Action) []Action { // (see the documentation for each error kind). // // Note that certain errors can make some actions become unavailable. -func (c *RunChecksContext) classifyRunChecksError(err error) (info errorInfo, outErr error) { +func (c *RunChecksContext) classifyRunChecksError(ctx context.Context, err error) (info errorInfo, outErr error) { defer func() { if outErr != nil { return @@ -579,7 +591,22 @@ func (c *RunChecksContext) classifyRunChecksError(err error) (info errorInfo, ou } if errors.Is(err, ErrNoSecureBoot) || errors.Is(err, ErrNoDeployedMode) { - return errorInfo{kind: ErrorKindInvalidSecureBootMode}, nil + varCtx := runChecksEnv.VarContext(ctx) + enabled, err := efi.ReadSecureBootVariable(varCtx) + if err != nil { + return errorInfo{}, fmt.Errorf("cannot read secure boot variable: %w", err) + } + mode, err := efi.ComputeSecureBootMode(varCtx) + if err != nil { + return errorInfo{}, fmt.Errorf("cannot compute secure boot mode: %w", err) + } + return errorInfo{ + kind: ErrorKindInvalidSecureBootMode, + args: SecureBootModeArg{ + Enabled: enabled, + Mode: mode, + }, + }, nil } if errors.Is(err, ErrWeakSecureBootAlgorithmDetected) { return errorInfo{kind: ErrorKindWeakSecureBootAlgorithmsDetected}, nil @@ -755,20 +782,14 @@ func (c *RunChecksContext) runAction(action Action, args map[string]json.RawMess } for i, kind := range kinds { - flag, ok := errorKindToProceedFlag[kind] - if !ok { - return NewWithKindAndActionsError( - ErrorKindInvalidArgument, - InvalidActionArgumentDetails{ - Field: fieldName, - Reason: InvalidActionArgumentReasonValue, - }, - nil, // actions - fmt.Errorf("invalid value for argument %q at index %d: %q does not support the %q action", fieldName, i, kind, ActionProceed), - ) + var ( + flag CheckFlags + ok bool + ) + if c.proceedFlags != nil { + flag, ok = c.proceedFlags[kind] } - - if c.proceedFlags&flag == 0 { + if !ok { return NewWithKindAndActionsError( ErrorKindInvalidArgument, InvalidActionArgumentDetails{ @@ -781,15 +802,17 @@ func (c *RunChecksContext) runAction(action Action, args map[string]json.RawMess } proceedFlags |= flag - c.proceedFlags &^= flag + delete(c.proceedFlags, kind) } } - if proceedFlags == CheckFlags(0) { + if proceedFlags == CheckFlags(0) && c.proceedFlags != nil { // Handle the case where no argument is supplied or // an empty []ErrorKind slice is supplied - proceedFlags = c.proceedFlags - c.proceedFlags = 0 + for _, flag := range c.proceedFlags { + proceedFlags |= flag + } + c.proceedFlags = nil } c.flags |= proceedFlags @@ -869,7 +892,7 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri c.expectedActions = nil // Reset the flags that would be enabled if ActionProceed is used. - c.proceedFlags = 0 + c.proceedFlags = nil // errInfo contains the error kind and arguments for each error. var errInfo []errorInfo @@ -879,7 +902,7 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri // the WithKindAndActionsError because some errors encountered here // may change the available actions. for _, e := range unwrapCompoundError(err) { - info, err := c.classifyRunChecksError(e) + info, err := c.classifyRunChecksError(ctx, e) if err != nil { return nil, NewWithKindAndActionsError( ErrorKindInternal, @@ -901,6 +924,8 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri // ordering these errors to appear after all other errors. var errsProceed []*WithKindAndActionsError + proceedFlags := make(map[ErrorKind]CheckFlags) + // Iterate over the error info, creating a WithKindAndActionsError // for each one with associated actions. for _, info := range errInfo { @@ -914,7 +939,11 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri ) } - if _, canProceed := errorKindToProceedFlag[info.kind]; !canProceed { + proceedFlag, canProceed := errorKindToProceedFlag[info.kind] + if !canProceed { + proceedFlag, canProceed = errorKindWithArgsToProceedFlag[errorKindWithArgs{kind: info.kind, args: info.args}] + } + if !canProceed { // This error kind doesn't support ActionProceed. Don't // permit it at all for now, waiting until all of the errors // we return support it. @@ -922,6 +951,7 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri errs = append(errs, NewWithKindAndActionsError(info.kind, info.args, actions, info.err)) } else { errsProceed = append(errsProceed, NewWithKindAndActionsError(info.kind, info.args, actions, info.err)) + proceedFlags[info.kind] = proceedFlag } c.expectedActions = append(c.expectedActions, actions...) @@ -931,14 +961,14 @@ func (c *RunChecksContext) Run(ctx context.Context, action Action, args map[stri // right now, and append these errors to the list of errors we return. for _, e := range errsProceed { if permitActionProceed { - flag := errorKindToProceedFlag[e.Kind] - c.proceedFlags |= flag e.Actions = insertActionProceed(e.Actions) } errs = append(errs, e) } - - if c.proceedFlags != 0 { + if permitActionProceed { + c.proceedFlags = proceedFlags + } + if len(c.proceedFlags) > 0 { // We are returning errors with ActionProceed enabled. c.expectedActions = append(c.expectedActions, ActionProceed) } diff --git a/efi/preinstall/checks_context_test.go b/efi/preinstall/checks_context_test.go index 8bcddf6c..3b79c8bd 100644 --- a/efi/preinstall/checks_context_test.go +++ b/efi/preinstall/checks_context_test.go @@ -2151,6 +2151,178 @@ C7E003CB c.Check(errs, HasLen, 0) } +func (s *runChecksContextSuite) TestRunGoodActionProceedPermitSecureBootUserMode(c *C) { + meiAttrs := map[string][]byte{ + "fw_ver": []byte(`0:16.1.27.2176 +0:16.1.27.2176 +0:16.0.15.1624 +`), + "fw_status": []byte(`94000245 +09F10506 +00000020 +00004000 +00041F03 +C7E003CB +`), + } + devices := []internal_efi.SysfsDevice{ + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( + "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, + )), + } + + errs := s.testRun(c, &testRunChecksContextRunParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), + efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + })), + efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0x13a: (3 << 1), 0xc80: 0x40000000}), + efitest.WithSysfsDevices(devices...), + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + ), + tpmPropertyModifiers: map[tpm2.Property]uint32{ + tpm2.PropertyNVCountersMax: 0, + tpm2.PropertyPSFamilyIndicator: 1, + tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), + }, + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + iterations: 2, + loadedImages: []secboot_efi.Image{ + &mockImage{ + contents: []byte("mock shim executable"), + digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + profileOpts: PCRProfileOptionsDefault, + actions: []actionAndArgs{ + {action: ActionNone}, + {action: ActionProceed}, + }, + checkIntermediateErrs: func(i int, errs []*WithKindAndActionsError) { + switch i { + case 0: + c.Assert(errs, HasLen, 1) + + c.Check(errs[0], ErrorMatches, `secure boot is enabled but not in deployed mode`) + c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( + ErrorKindInvalidSecureBootMode, + SecureBootModeArg{ + Enabled: true, + Mode: efi.UserMode, + }, + []Action{ActionRebootToFWSettings, ActionProceed}, + ErrNoDeployedMode, + )) + } + }, + expectedPcrAlg: tpm2.HashAlgorithmSHA256, + expectedUsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + expectedFlags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + expectedAcceptedErrors: map[ErrorKind]json.RawMessage{ + ErrorKindInvalidSecureBootMode: nil, + }, + expectedWarningsMatch: `4 errors detected: +- error with platform config \(PCR1\) measurements: generating profiles for PCR 1 is not supported yet, see https://github.com/canonical/secboot/issues/322 +- error with drivers and apps config \(PCR3\) measurements: generating profiles for PCR 3 is not supported yet, see https://github.com/canonical/secboot/issues/341 +- error with boot manager config \(PCR5\) measurements: generating profiles for PCR 5 is not supported yet, see https://github.com/canonical/secboot/issues/323 +- secure boot is enabled but not in deployed mode +`, + }) + c.Check(errs, HasLen, 0) +} + +func (s *runChecksContextSuite) TestRunGoodPermitSecureBootUserModeFromInitialFlags(c *C) { + meiAttrs := map[string][]byte{ + "fw_ver": []byte(`0:16.1.27.2176 +0:16.1.27.2176 +0:16.0.15.1624 +`), + "fw_status": []byte(`94000245 +09F10506 +00000020 +00004000 +00041F03 +C7E003CB +`), + } + devices := []internal_efi.SysfsDevice{ + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( + "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, + )), + } + + errs := s.testRun(c, &testRunChecksContextRunParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), + efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + })), + efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0x13a: (3 << 1), 0xc80: 0x40000000}), + efitest.WithSysfsDevices(devices...), + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + ), + tpmPropertyModifiers: map[tpm2.Property]uint32{ + tpm2.PropertyNVCountersMax: 0, + tpm2.PropertyPSFamilyIndicator: 1, + tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), + }, + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + initialFlags: PermitSecureBootUserMode, + loadedImages: []secboot_efi.Image{ + &mockImage{ + contents: []byte("mock shim executable"), + digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + profileOpts: PCRProfileOptionsDefault, + actions: []actionAndArgs{{action: ActionNone}}, + expectedPcrAlg: tpm2.HashAlgorithmSHA256, + expectedUsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + expectedFlags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + expectedAcceptedErrors: map[ErrorKind]json.RawMessage{ + ErrorKindInvalidSecureBootMode: nil, + }, + expectedWarningsMatch: `4 errors detected: +- error with platform config \(PCR1\) measurements: generating profiles for PCR 1 is not supported yet, see https://github.com/canonical/secboot/issues/322 +- error with drivers and apps config \(PCR3\) measurements: generating profiles for PCR 3 is not supported yet, see https://github.com/canonical/secboot/issues/341 +- error with boot manager config \(PCR5\) measurements: generating profiles for PCR 5 is not supported yet, see https://github.com/canonical/secboot/issues/323 +- secure boot is enabled but not in deployed mode +`, + }) + c.Check(errs, HasLen, 0) +} + func (s *runChecksContextSuite) TestRunGoodActionProceedPermitPreOSSecureBootAuthByEnrolledDigests(c *C) { // Test that ActionProceed enables the PermitPreOSSecureBootAuthByEnrolledDigests flag. As // this test generates 2 errors, it also tests the case where ActionProceed can @@ -5695,75 +5867,6 @@ C7E003CB c.Check(errs[0], DeepEquals, NewWithKindAndActionsError(ErrorKindPCRUnusable, PCRUnusableArg(4), []Action{ActionContactOEM}, errs[0].Unwrap())) } -func (s *runChecksContextSuite) TestRunBadInvalidSecureBootMode(c *C) { - // Test the error case where PCR7 is mandatory with the supplied profile options, - // but is marked invalid because the system is not in deployed mode. Note that it is - // my intention to support user mode in the future. - meiAttrs := map[string][]byte{ - "fw_ver": []byte(`0:16.1.27.2176 -0:16.1.27.2176 -0:16.0.15.1624 -`), - "fw_status": []byte(`94000245 -09F10506 -00000020 -00004000 -00041F03 -C7E003CB -`), - } - devices := []internal_efi.SysfsDevice{ - efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), - efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), - efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( - "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, - )), - } - - errs := s.testRun(c, &testRunChecksContextRunParams{ - env: efitest.NewMockHostEnvironmentWithOpts( - efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), - efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), - efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ - Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - })), - efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0x13a: (3 << 1), 0xc80: 0x40000000}), - efitest.WithSysfsDevices(devices...), - efitest.WithMockVars(efitest.MockVars{ - {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, - {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, - {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, - {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, - {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, - {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), - ), - tpmPropertyModifiers: map[tpm2.Property]uint32{ - tpm2.PropertyNVCountersMax: 0, - tpm2.PropertyPSFamilyIndicator: 1, - tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), - }, - enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - loadedImages: []secboot_efi.Image{ - &mockImage{ - contents: []byte("mock shim executable"), - digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), - signatures: []*efi.WinCertificateAuthenticode{ - efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), - }, - }, - &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, - &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, - }, - profileOpts: PCRProfileOptionsDefault, - actions: []actionAndArgs{{action: ActionNone}}, - expectedPcrAlg: tpm2.HashAlgorithmSHA256, - }) - c.Check(errs, HasLen, 1) - c.Check(errs[0], ErrorMatches, `error with secure boot policy \(PCR7\) measurements: deployed mode should be enabled in order to generate secure boot profiles`) - c.Check(errs[0], DeepEquals, NewWithKindAndActionsError(ErrorKindInvalidSecureBootMode, nil, []Action{ActionRebootToFWSettings}, errs[0].Unwrap())) -} - func (s *runChecksContextSuite) TestRunBadInvalidSecureBootModeSecureBootDisabled(c *C) { // Test the error case where PCR7 is mandatory with the supplied profile options, // but is marked invalid because secure boot is disabled. @@ -5830,7 +5933,15 @@ C7E003CB }) c.Check(errs, HasLen, 1) c.Check(errs[0], ErrorMatches, `error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles`) - c.Check(errs[0], DeepEquals, NewWithKindAndActionsError(ErrorKindInvalidSecureBootMode, nil, []Action{ActionRebootToFWSettings}, errs[0].Unwrap())) + c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( + ErrorKindInvalidSecureBootMode, + SecureBootModeArg{ + Enabled: false, + Mode: efi.UserMode, + }, + []Action{ActionRebootToFWSettings}, + errs[0].Unwrap(), + )) } func (s *runChecksContextSuite) TestRunBadInvalidSecureBootModeSecureBootDisabledAndNoSBATLevel(c *C) { @@ -5902,7 +6013,15 @@ C7E003CB }) c.Check(errs, HasLen, 1) c.Check(errs[0], ErrorMatches, `error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles`) - c.Check(errs[0], DeepEquals, NewWithKindAndActionsError(ErrorKindInvalidSecureBootMode, nil, []Action{ActionRebootToFWSettings}, errs[0].Unwrap())) + c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( + ErrorKindInvalidSecureBootMode, + SecureBootModeArg{ + Enabled: false, + Mode: efi.UserMode, + }, + []Action{ActionRebootToFWSettings}, + errs[0].Unwrap(), + )) } func (s *runChecksContextSuite) TestRunBadNoSecureBootPolicySupport(c *C) { @@ -6113,6 +6232,82 @@ C7E003CB )) } +func (s *runChecksContextSuite) TestRunBadInvalidSecureBootModeNoDeployedMode(c *C) { + // Test the error case where PCR7 is mandatory with the supplied profile options, + // but is marked invalid because the system is not in deployed mode. + meiAttrs := map[string][]byte{ + "fw_ver": []byte(`0:16.1.27.2176 +0:16.1.27.2176 +0:16.0.15.1624 +`), + "fw_status": []byte(`94000245 +09F10506 +00000020 +00004000 +00041F03 +C7E003CB +`), + } + devices := []internal_efi.SysfsDevice{ + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( + "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, + )), + } + + errs := s.testRun(c, &testRunChecksContextRunParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), + efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + })), + efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0x13a: (3 << 1), 0xc80: 0x40000000}), + efitest.WithSysfsDevices(devices...), + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + ), + tpmPropertyModifiers: map[tpm2.Property]uint32{ + tpm2.PropertyNVCountersMax: 0, + tpm2.PropertyPSFamilyIndicator: 1, + tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), + }, + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + loadedImages: []secboot_efi.Image{ + &mockImage{ + contents: []byte("mock shim executable"), + digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + profileOpts: PCRProfileOptionsDefault, + actions: []actionAndArgs{{action: ActionNone}}, + expectedPcrAlg: tpm2.HashAlgorithmSHA256, + }) + c.Assert(errs, HasLen, 1) + c.Check(errs[0], ErrorMatches, `secure boot is enabled but not in deployed mode`) + c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( + ErrorKindInvalidSecureBootMode, + SecureBootModeArg{ + Enabled: true, + Mode: efi.UserMode, + }, + []Action{ActionRebootToFWSettings, ActionProceed}, + ErrNoDeployedMode, + )) +} + // TODO: This is disabled temporarily until a follow-up PR which adds warnings to the returned errors // if RunChecks is going to return one or more errors anyway. //func (s *runChecksContextSuite) TestRunChecksBadTPMHierarchiesOwnedAndNoSecureBootPolicySupport(c *C) { @@ -6838,129 +7033,6 @@ C7E003CB )) } -func (s *runChecksContextSuite) TestRunChecksActionProceedUnsupportedErrorKind(c *C) { - // Test that passing an unsupported ErrorKind as an argument to ActionProceed - // generates an error. - meiAttrs := map[string][]byte{ - "fw_ver": []byte(`0:16.1.27.2176 -0:16.1.27.2176 -0:16.0.15.1624 -`), - "fw_status": []byte(`94000245 -09F10506 -00000020 -00004000 -00041F03 -C7E003CB -`), - } - devices := []internal_efi.SysfsDevice{ - efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), - efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), - efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( - "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, - )), - } - - errs := s.testRun(c, &testRunChecksContextRunParams{ - env: efitest.NewMockHostEnvironmentWithOpts( - efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), - efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), - efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ - Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - IncludeDriverLaunch: true, - })), - efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0x13a: (3 << 1), 0xc80: 0x40000000}), - efitest.WithSysfsDevices(devices...), - efitest.WithMockVars(efitest.MockVars{ - {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, - {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, - {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, - {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}}, - {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, - {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), - ), - tpmPropertyModifiers: map[tpm2.Property]uint32{ - tpm2.PropertyNVCountersMax: 0, - tpm2.PropertyPSFamilyIndicator: 1, - tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), - }, - enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - iterations: 2, - loadedImages: []secboot_efi.Image{ - &mockImage{ - contents: []byte("mock shim executable"), - digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), - signatures: []*efi.WinCertificateAuthenticode{ - efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), - }, - }, - &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, - &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, - }, - profileOpts: PCRProfileOptionsDefault, - actions: []actionAndArgs{ - {action: ActionNone}, - {action: ActionProceed, args: ActionProceedArgs{ErrorKindInvalidSecureBootMode}}, - }, - checkIntermediateErrs: func(i int, errs []*WithKindAndActionsError) { - switch i { - case 0: - c.Check(errs, HasLen, 1) - imageInfo := []*LoadedImageInfo{ - { - DevicePath: efi.DevicePath{ - &efi.ACPIDevicePathNode{ - HID: 0x0a0341d0, - UID: 0x0, - }, - &efi.PCIDevicePathNode{ - Function: 0x1c, - Device: 0x2, - }, - &efi.PCIDevicePathNode{ - Function: 0x0, - Device: 0x0, - }, - &efi.MediaRelOffsetRangeDevicePathNode{ - StartingOffset: 0x38, - EndingOffset: 0x11dff, - }, - }, - DigestAlg: tpm2.HashAlgorithmSHA256, - Digest: testutil.DecodeHexString(c, "1e94aaed2ad59a4409f3230dca2ad8c03ef8e3fde77cc47dc7b81bb8b242f3e6"), - }, - } - - c.Check(errs[0], ErrorMatches, `addon drivers were detected: -- \[no description\] path=\\PciRoot\(0x0\)\\Pci\(0x2,0x1c\)\\Pci\(0x0,0x0\)\\Offset\(0x38,0x11dff\) authenticode-digest=TPM_ALG_SHA256:1e94aaed2ad59a4409f3230dca2ad8c03ef8e3fde77cc47dc7b81bb8b242f3e6 -`) - c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( - ErrorKindAddonDriversPresent, - LoadedImagesInfoArg(imageInfo), - []Action{ActionProceed}, - &AddonDriversPresentError{ - Drivers: imageInfo, - }, - )) - } - }, - expectedPcrAlg: tpm2.HashAlgorithmSHA256, - }) - c.Assert(errs, HasLen, 1) - c.Check(errs[0], ErrorMatches, `invalid value for argument "error-kinds" at index 0: "invalid-secure-boot-mode" does not support the "proceed" action`) - c.Check(errs[0], DeepEquals, NewWithKindAndActionsError( - ErrorKindInvalidArgument, - InvalidActionArgumentDetails{ - Field: "error-kinds", - Reason: InvalidActionArgumentReasonValue, - }, - nil, - errs[0].Unwrap(), - )) -} - func (s *runChecksContextSuite) TestRunChecksActionProceedUnexpectedErrorKind1(c *C) { // Test that passing an unexpected ErrorKind as an argument to ActionProceed // generates an error. diff --git a/efi/preinstall/checks_test.go b/efi/preinstall/checks_test.go index 8359de09..0bba5ec2 100644 --- a/efi/preinstall/checks_test.go +++ b/efi/preinstall/checks_test.go @@ -2776,7 +2776,7 @@ C7E003CB {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), ), tpmPropertyModifiers: map[tpm2.Property]uint32{ tpm2.PropertyNVCountersMax: 0, @@ -2818,7 +2818,92 @@ C7E003CB c.Check(errors.As(warning, &bmce), testutil.IsTrue) warning = warnings[3] - c.Check(warning, ErrorMatches, `error with secure boot policy \(PCR7\) measurements: deployed mode should be enabled in order to generate secure boot profiles`) + c.Check(warning, ErrorMatches, `error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles`) + var sbpe *SecureBootPolicyPCRError + c.Assert(errors.As(warning, &sbpe), testutil.IsTrue) + c.Check(errors.Is(sbpe, ErrNoSecureBoot), testutil.IsTrue) +} + +func (s *runChecksSuite) TestRunChecksGoodNoSecureBootDeployedMode(c *C) { + meiAttrs := map[string][]byte{ + "fw_ver": []byte(`0:16.1.27.2176 +0:16.1.27.2176 +0:16.0.15.1624 +`), + "fw_status": []byte(`94000245 +09F10506 +00000020 +00004000 +00041F03 +C7E003CB +`), + } + devices := []internal_efi.SysfsDevice{ + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar0", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/virtual/iommu/dmar1", nil, "iommu", nil, nil), + efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", meiAttrs, efitest.NewMockSysfsDevice( + "/sys/devices/pci0000:00:16:0", map[string]string{"DRIVER": "mei_me"}, "pci", nil, nil, + )), + } + + warnings, err := s.testRunChecks(c, &testRunChecksParams{ + env: efitest.NewMockHostEnvironmentWithOpts( + efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), + efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), + efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0xc80: 0x40000000, 0x13a: (3 << 1)}), + efitest.WithSysfsDevices(devices...), + efitest.WithMockVars(efitest.MockVars{ + {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "BootCurrent", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x3, 0x0}}, + {Name: "BootOptionSupport", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x13, 0x03, 0x00, 0x00}}, + {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, + {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + ), + tpmPropertyModifiers: map[tpm2.Property]uint32{ + tpm2.PropertyNVCountersMax: 0, + tpm2.PropertyPSFamilyIndicator: 1, + tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC), + }, + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + flags: PermitNoPlatformConfigProfileSupport | PermitNoDriversAndAppsConfigProfileSupport | PermitNoBootManagerConfigProfileSupport | PermitSecureBootUserMode, + loadedImages: []secboot_efi.Image{ + &mockImage{ + contents: []byte("mock shim executable"), + digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"), + signatures: []*efi.WinCertificateAuthenticode{ + efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4), + }, + }, + &mockImage{contents: []byte("mock grub executable"), digest: testutil.DecodeHexString(c, "d5a9780e9f6a43c2e53fe9fda547be77f7783f31aea8013783242b040ff21dc0")}, + &mockImage{contents: []byte("mock kernel executable"), digest: testutil.DecodeHexString(c, "2ddfbd91fa1698b0d133c38ba90dbba76c9e08371ff83d03b5fb4c2e56d7e81f")}, + }, + expectedPcrAlg: tpm2.HashAlgorithmSHA256, + expectedUsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + expectedFlags: NoPlatformConfigProfileSupport | NoDriversAndAppsConfigProfileSupport | NoBootManagerConfigProfileSupport, + }) + c.Assert(err, IsNil) + c.Assert(warnings, HasLen, 4) + + warning := warnings[0] + c.Check(warning, ErrorMatches, `error with platform config \(PCR1\) measurements: generating profiles for PCR 1 is not supported yet, see https://github.com/canonical/secboot/issues/322`) + var pce *PlatformConfigPCRError + c.Check(errors.As(warning, &pce), testutil.IsTrue) + + warning = warnings[1] + c.Check(warning, ErrorMatches, `error with drivers and apps config \(PCR3\) measurements: generating profiles for PCR 3 is not supported yet, see https://github.com/canonical/secboot/issues/341`) + var dce *DriversAndAppsConfigPCRError + c.Check(errors.As(warning, &dce), testutil.IsTrue) + + warning = warnings[2] + c.Check(warning, ErrorMatches, `error with boot manager config \(PCR5\) measurements: generating profiles for PCR 5 is not supported yet, see https://github.com/canonical/secboot/issues/323`) + var bmce *BootManagerConfigPCRError + c.Check(errors.As(warning, &bmce), testutil.IsTrue) + + warning = warnings[3] + c.Check(warning, ErrorMatches, `secure boot is enabled but not in deployed mode`) c.Check(errors.Is(warning, ErrNoDeployedMode), testutil.IsTrue) } @@ -4403,7 +4488,7 @@ C7E003CB {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), ), tpmPropertyModifiers: map[tpm2.Property]uint32{ tpm2.PropertyNVCountersMax: 0, @@ -4425,7 +4510,7 @@ C7E003CB }, expectedPcrAlg: tpm2.HashAlgorithmSHA256, }) - c.Check(err, ErrorMatches, `error with secure boot policy \(PCR7\) measurements: deployed mode should be enabled in order to generate secure boot profiles`) + c.Check(err, ErrorMatches, `error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles`) var ce CompoundError c.Assert(err, Implements, &ce) @@ -4435,10 +4520,10 @@ C7E003CB var sbe *SecureBootPolicyPCRError c.Assert(errors.As(errs[0], &sbe), testutil.IsTrue) - c.Check(errors.Is(sbe, ErrNoDeployedMode), testutil.IsTrue) + c.Check(errors.Is(sbe, ErrNoSecureBoot), testutil.IsTrue) } -func (s *runChecksSuite) TestRunChecksBadNoSecureBootPolicyProfileSupportSecureBootDisabled(c *C) { +func (s *runChecksSuite) TestRunChecksBadNoSecureBootPolicyProfileSupportNoDeployedMode(c *C) { meiAttrs := map[string][]byte{ "fw_ver": []byte(`0:16.1.27.2176 0:16.1.27.2176 @@ -4464,10 +4549,7 @@ C7E003CB env: efitest.NewMockHostEnvironmentWithOpts( efitest.WithVirtMode(internal_efi.VirtModeNone, internal_efi.DetectVirtModeAll), efitest.WithTPMDevice(newTpmDevice(tpm2_testutil.NewTransportBackedDevice(s.Transport, false, 1), nil, tpm2_device.ErrNoPPI)), - efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{ - Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, - SecureBootDisabled: true, - })), + efitest.WithLog(efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})), efitest.WithAMD64Environment("GenuineIntel", []uint64{cpuid.SDBG, cpuid.SMX}, 4, map[uint32]uint64{0xc80: 0x40000000, 0x13a: (3 << 1)}), efitest.WithSysfsDevices(devices...), efitest.WithMockVars(efitest.MockVars{ @@ -4477,7 +4559,7 @@ C7E003CB {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), ), tpmPropertyModifiers: map[tpm2.Property]uint32{ tpm2.PropertyNVCountersMax: 0, @@ -4499,7 +4581,7 @@ C7E003CB }, expectedPcrAlg: tpm2.HashAlgorithmSHA256, }) - c.Check(err, ErrorMatches, `error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles`) + c.Check(err, ErrorMatches, `secure boot is enabled but not in deployed mode`) var ce CompoundError c.Assert(err, Implements, &ce) @@ -4507,9 +4589,7 @@ C7E003CB errs := ce.Unwrap() c.Assert(errs, HasLen, 1) - var sbe *SecureBootPolicyPCRError - c.Assert(errors.As(errs[0], &sbe), testutil.IsTrue) - c.Check(errors.Is(sbe, ErrNoSecureBoot), testutil.IsTrue) + c.Check(errors.Is(errs[0], ErrNoDeployedMode), testutil.IsTrue) } func (s *runChecksSuite) TestRunChecksBadNoSecureBootPolicyProfileSupportSecureBootDisabledAndNoSBATLevel(c *C) { @@ -4627,7 +4707,7 @@ C7E003CB {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}}, {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), + }.SetSecureBoot(false).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))), ), tpmPropertyModifiers: map[tpm2.Property]uint32{ tpm2.PropertyNVCountersMax: 0, @@ -4657,7 +4737,7 @@ C7E003CB c.Assert(err, ErrorMatches, `2 errors detected: - error with TPM2 device: one or more of the TPM hierarchies is already owned: - TPM_RH_LOCKOUT has an authorization value -- error with secure boot policy \(PCR7\) measurements: deployed mode should be enabled in order to generate secure boot profiles +- error with secure boot policy \(PCR7\) measurements: secure boot should be enabled in order to generate secure boot profiles `) var ce CompoundError @@ -4673,6 +4753,7 @@ C7E003CB var sbpe *SecureBootPolicyPCRError c.Check(errors.As(errs[1], &sbpe), testutil.IsTrue) + c.Check(errors.Is(sbpe, ErrNoSecureBoot), testutil.IsTrue) } func (s *runChecksSuite) TestRunChecksBadInsufficientDMAProtectionAndNoBootManagerCodeProfileSupport(c *C) { diff --git a/efi/preinstall/error_kinds.go b/efi/preinstall/error_kinds.go index ab276ac8..a570c52f 100644 --- a/efi/preinstall/error_kinds.go +++ b/efi/preinstall/error_kinds.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" + efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" ) @@ -287,3 +288,75 @@ type InvalidActionArgumentDetails struct { func (a *InvalidActionArgumentDetails) String() string { return fmt.Sprintf("invalid action argument %q: invalid %s", a.Field, a.Reason) } + +type secureBootModeJson efi.SecureBootMode + +func (m secureBootModeJson) MarshalJSON() ([]byte, error) { + var s string + switch efi.SecureBootMode(m) { + case efi.SetupMode: + s = "setup" + case efi.AuditMode: + s = "audit" + case efi.UserMode: + s = "user" + case efi.DeployedMode: + s = "deployed" + default: + return nil, errors.New("invalid secure boot mode") + } + + return json.Marshal(s) +} + +func (m *secureBootModeJson) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + switch s { + case "setup": + *m = secureBootModeJson(efi.SetupMode) + case "audit": + *m = secureBootModeJson(efi.AuditMode) + case "user": + *m = secureBootModeJson(efi.UserMode) + case "deployed": + *m = secureBootModeJson(efi.DeployedMode) + default: + return fmt.Errorf("invalid secure boot mode %q", s) + } + + return nil +} + +type secureBootModeArgJson struct { + Enabled bool `json:"enabled"` + Mode secureBootModeJson `json:"mode"` +} + +type SecureBootModeArg struct { + Enabled bool + Mode efi.SecureBootMode +} + +func (a SecureBootModeArg) MarshalJSON() ([]byte, error) { + return json.Marshal(secureBootModeArgJson{ + Enabled: a.Enabled, + Mode: secureBootModeJson(a.Mode), + }) +} + +func (a *SecureBootModeArg) UnmarshalJSON(data []byte) error { + var m secureBootModeArgJson + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + *a = SecureBootModeArg{ + Enabled: m.Enabled, + Mode: efi.SecureBootMode(m.Mode), + } + return nil +} diff --git a/efi/preinstall/errors.go b/efi/preinstall/errors.go index 5ec2faee..555dbcff 100644 --- a/efi/preinstall/errors.go +++ b/efi/preinstall/errors.go @@ -926,10 +926,8 @@ var ( ErrNoSecureBoot = errors.New("secure boot should be enabled in order to generate secure boot profiles") // ErrNoDeployedMode is returned wrapped in SecureBootPolicyPCRError to indicate - // that deployed mode is not enabled. In the future, this package will permit - // generation of profiles on systems that implement UEFI >= 2.5 that are in user - // mode, but this is not the case today. - ErrNoDeployedMode = errors.New("deployed mode should be enabled in order to generate secure boot profiles") + // that deployed mode is not enabled. + ErrNoDeployedMode = errors.New("secure boot is enabled but not in deployed mode") // ErrWeakSecureBootAlgorithmDetected is returned wrapped in a type that implements CompoundError and // indicates that weak algorithms were detected during secure boot verification, such as authenticating diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index 0ba171e7..2e38a1ca 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -58,6 +58,7 @@ const ( InsufficientDMAProtectionDetected = insufficientDMAProtectionDetected SecureBootIncludesWeakAlg = secureBootIncludesWeakAlg SecureBootPreOSVerificationIncludesDigest = secureBootPreOSVerificationIncludesDigest + SecureBootNoDeployedMode = secureBootNoDeployedMode StartupLocalityNotProtected = startupLocalityNotProtected ) diff --git a/efi/preinstall/profile.go b/efi/preinstall/profile.go index 47f94b84..aee03c46 100644 --- a/efi/preinstall/profile.go +++ b/efi/preinstall/profile.go @@ -474,6 +474,11 @@ func (o *pcrProfileAutoSetPcrsOption) ApplyOptionTo(visitor internal_efi.PCRProf return fmt.Errorf("cannot add DMA allow insufficient protection profile option: %w", err) } } + if _, permitted := o.result.AcceptedErrors[ErrorKindInvalidSecureBootMode]; permitted { + if err := secboot_efi.WithAllowSecureBootUserMode().ApplyOptionTo(visitor); err != nil { + return fmt.Errorf("cannot add profile option for secure boot user mode: %w", err) + } + } return nil } diff --git a/efi/preinstall/profile_test.go b/efi/preinstall/profile_test.go index 1a3fdef4..0c8375ab 100644 --- a/efi/preinstall/profile_test.go +++ b/efi/preinstall/profile_test.go @@ -569,3 +569,27 @@ func (s *profileSuite) TestWithAutoTCGPCRInsufficientDMAProtection(c *C) { }, }) } + +func (s *profileSuite) TestWithAutoTCGPCRInvalidSecureBootMode(c *C) { + result := &CheckResult{ + PCRAlg: tpm2.HashAlgorithmSHA256, + UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))}, + AcceptedErrors: map[ErrorKind]json.RawMessage{ErrorKindInvalidSecureBootMode: nil}, + } + profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault) + + profile = profile.Options(PCRProfileOptionsDefault) + + visitor := &mockPcrProfileOptionVisitor{ + imageLoadParams: []internal_efi.LoadParams{{}}, + } + c.Check(profile.ApplyOptionTo(visitor), IsNil) + c.Check(visitor.imageLoadParams, DeepEquals, []internal_efi.LoadParams{ + { + "include_secure_boot_user_mode": false, + }, + { + "include_secure_boot_user_mode": true, + }, + }) +} diff --git a/efi/secureboot.go b/efi/secureboot.go index 733fcfd1..28615747 100644 --- a/efi/secureboot.go +++ b/efi/secureboot.go @@ -315,3 +315,43 @@ func (o allowInsufficientDmaProtectionOption) ApplyOptionTo(visitor internal_efi func WithAllowInsufficientDmaProtection() PCRProfileOption { return allowInsufficientDmaProtectionOption{} } + +const ( + // includeSecureBootUserModeParamKey is used to indicate that user mode related + // measurements should be included in the secure boot PCR profile if the system is + // in user mode. + includeSecureBootUserModeParamKey = "include_secure_boot_user_mode" +) + +type allowSecureBootUserModeOption struct{} + +func (o allowSecureBootUserModeOption) ApplyOptionTo(visitor internal_efi.PCRProfileOptionVisitor) error { + visitor.AddImageLoadParams(func(params ...loadParams) []loadParams { + var out []loadParams + for _, v := range []bool{false, true} { + var newParams []loadParams + for _, p := range params { + newParams = append(newParams, p.Clone()) + } + for _, p := range newParams { + p[includeSecureBootUserModeParamKey] = v + } + out = append(out, newParams...) + } + return out + }) + return nil +} + +// WithAllowSecureBootUserMode can be supplied to AddPCRProfile to allow for secure boot +// PCR profiles that support user mode to be generated on systems where user mode is +// currently enabled. This is opt-in to ensure that a system that was originally in +// deployed mode doesn't automatically regenerate a PCR profile for user mode in the case +// where the firmware settings are inadvertently degraded. +// +// If the system is in user mode, PCR profile branches will be generated both for user mode +// and deployed mode to allow a system to be placed back into deployed mode without making +// the generated policy invalid. If the system is in deployed mode, this option has no effect. +func WithAllowSecureBootUserMode() PCRProfileOption { + return allowSecureBootUserModeOption{} +}