diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index 2adfe999..196e420d 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -285,7 +285,8 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartPlatformFirmwareProfile(c *C) pcrs: MakePcrFlags(internal_efi.PlatformFirmwarePCR), expectedEvents: []*mockPcrBranchEvent{ {pcr: 0, eventType: mockPcrBranchResetEvent}, - {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "d0ff5974b6aa52cf562bea5921840c032a860a91a3512f7fe8f768f6bbe005f6")}, + {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "cd1137dfa2bfba51973100d73d78d9f496e089fd246fe980fadc668b4efc9443")}, + {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "5ca5e6acb83d626a42b53ddc5a2fe04d6a4b2f045bb07f6d9baf0e82900d7bbe")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "aef237d4703e8936530141636186a9f249fa39e194f02f668cd328bd5902cf03")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "8b0eec99d3cccc081edb98c3a2aa74b99a02b785bd74513e1cf7401e99121e80")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, @@ -300,7 +301,8 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartPlatformFirmwareProfileSL3(c * pcrs: MakePcrFlags(internal_efi.PlatformFirmwarePCR), expectedEvents: []*mockPcrBranchEvent{ {pcr: 0, eventType: mockPcrBranchResetCRTMPCREvent, locality: 3}, - {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "d0ff5974b6aa52cf562bea5921840c032a860a91a3512f7fe8f768f6bbe005f6")}, + {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "cd1137dfa2bfba51973100d73d78d9f496e089fd246fe980fadc668b4efc9443")}, + {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "5ca5e6acb83d626a42b53ddc5a2fe04d6a4b2f045bb07f6d9baf0e82900d7bbe")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "aef237d4703e8936530141636186a9f249fa39e194f02f668cd328bd5902cf03")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "8b0eec99d3cccc081edb98c3a2aa74b99a02b785bd74513e1cf7401e99121e80")}, {pcr: 0, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, diff --git a/efi/pcr_profile_test.go b/efi/pcr_profile_test.go index cbb76685..9f3e2956 100644 --- a/efi/pcr_profile_test.go +++ b/efi/pcr_profile_test.go @@ -745,7 +745,7 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithExtraProfiles(c *C) { expected: []tpm2.PCRValues{ { tpm2.HashAlgorithmSHA256: { - 0: testutil.DecodeHexString(c, "3d2b11b4c5cb623acbde6d14205217e47ebd368eab861e4fed782bb99be4598a"), + 0: testutil.DecodeHexString(c, "a6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69"), 2: testutil.DecodeHexString(c, "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"), 7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"), @@ -754,7 +754,7 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithExtraProfiles(c *C) { }, { tpm2.HashAlgorithmSHA256: { - 0: testutil.DecodeHexString(c, "3d2b11b4c5cb623acbde6d14205217e47ebd368eab861e4fed782bb99be4598a"), + 0: testutil.DecodeHexString(c, "a6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69"), 2: testutil.DecodeHexString(c, "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 4: testutil.DecodeHexString(c, "c731a39b7fc6475c7d8a9264e704902157c7cee40c22f59fa1690ea99ff70c67"), 7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"), @@ -801,7 +801,7 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithPlatformFirmwareProfileSL3(c expected: []tpm2.PCRValues{ { tpm2.HashAlgorithmSHA256: { - 0: testutil.DecodeHexString(c, "25a58800ba22dff433a8bb1b5084a53ddf02dc71f204053b38036fe1c0f146e2"), + 0: testutil.DecodeHexString(c, "b0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef"), 2: testutil.DecodeHexString(c, "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"), 7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"), @@ -810,7 +810,7 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithPlatformFirmwareProfileSL3(c }, { tpm2.HashAlgorithmSHA256: { - 0: testutil.DecodeHexString(c, "25a58800ba22dff433a8bb1b5084a53ddf02dc71f204053b38036fe1c0f146e2"), + 0: testutil.DecodeHexString(c, "b0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef"), 2: testutil.DecodeHexString(c, "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"), 4: testutil.DecodeHexString(c, "c731a39b7fc6475c7d8a9264e704902157c7cee40c22f59fa1690ea99ff70c67"), 7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"), diff --git a/efi/preinstall/check_tcglog.go b/efi/preinstall/check_tcglog.go new file mode 100644 index 00000000..6d291a12 --- /dev/null +++ b/efi/preinstall/check_tcglog.go @@ -0,0 +1,501 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall + +import ( + "bytes" + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + + "github.com/canonical/go-tpm2" + "github.com/canonical/tcglog-parser" + internal_efi "github.com/snapcore/secboot/internal/efi" +) + +var ( + // supportedAlgs specifies the supported PCR banks, in order of preference. + // XXX: We disallow SHA-1 here - perhaps this should be optionally permitted? + supportedAlgs = []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA512, tpm2.HashAlgorithmSHA384, tpm2.HashAlgorithmSHA256} + + // supportedPcrs specifies all of the TCG defined PCRs, although we don't actually + // support generating profiles for all of them at the moment, nor are we likely to + // for some of them in the future. + supportedPcrs = []tpm2.Handle{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + } +) + +// NoSuitablePCRAlgorithmError is returned wrapped from [RunChecks] if there is no suitable PCR bank +// where the log matches the TPM values when reconstructed. As multiple errors can occur during +// testing (multiple banks and multiple PCRs), this error tries to keep as much information as +// possible +type NoSuitablePCRAlgorithmError struct { + bankErrs map[tpm2.HashAlgorithmId]error + pcrErrs map[tpm2.HashAlgorithmId]map[tpm2.Handle]error +} + +func (e *NoSuitablePCRAlgorithmError) Error() string { + w := new(bytes.Buffer) + fmt.Fprintf(w, "no suitable PCR algorithm available:\n") + for _, alg := range supportedAlgs { + if err, isErr := e.bankErrs[alg]; isErr { + // We have a general error for this PCR bank + fmt.Fprintf(w, "- %v: %v.\n", alg, err) + } + pcrErrs, hasPcrErrs := e.pcrErrs[alg] + if !hasPcrErrs { + // WE have no individual PCR errors for this bank + continue + } + for _, pcr := range supportedPcrs { + if err, isErr := pcrErrs[pcr]; isErr { + // We have an error for this PCR + fmt.Fprintf(w, "- %v(PCR%d): %v.\n", alg, pcr, err) + } + } + } + return w.String() +} + +// UNwrapBankError returns the error associated with the specified PCR bank if one +// occurred, or nil if none occurred. +func (e *NoSuitablePCRAlgorithmError) UnwrapBankError(alg tpm2.HashAlgorithmId) error { + return e.bankErrs[alg] +} + +// UnwrapPCRError returns the error associated with the specified PCR in the specified +// bank if one occurred, or nil if none occurred. +func (e *NoSuitablePCRAlgorithmError) UnwrapPCRError(alg tpm2.HashAlgorithmId, pcr tpm2.Handle) error { + pcrErrs, exists := e.pcrErrs[alg] + if !exists { + return nil + } + return pcrErrs[pcr] +} + +// setBankErr sets an error for an entire PCR bank +func (e *NoSuitablePCRAlgorithmError) setBankErr(alg tpm2.HashAlgorithmId, err error) { + if e.bankErrs == nil { + e.bankErrs = make(map[tpm2.HashAlgorithmId]error) + } + e.bankErrs[alg] = err +} + +// setPcrErrs sets errors for individual PCRs associated with a bank +func (e *NoSuitablePCRAlgorithmError) setPcrErrs(results *pcrBankResults) { + if e.pcrErrs == nil { + e.pcrErrs = make(map[tpm2.HashAlgorithmId]map[tpm2.Handle]error) + } + e.pcrErrs[results.Alg] = results.pcrErrs() +} + +// pcrResult represents the result of reconstructing the log for a single PCR in a single bank +type pcrResults struct { + mandatory bool // Errors from testing this PCR can't be ignored + initialValue tpm2.Digest // The initial value for the PCR. Initialized to the size of the digest of the bank during construction. + logValue tpm2.Digest // The expected TPM PCR value from the reconstructed log. Initialized to the same value as initialValue during construction. + pcrValue tpm2.Digest // The actual TPM PCR value. Will be empty until set by setPcrValue + + err error // This is set for any error that occurred. +} + +// Ok indicates that the reconstructed log is consistent with the TPM PCR value. +func (r *pcrResults) Ok() bool { + return r.Err() == nil +} + +// Error returns any error that occurred when checking the log consistency with the +// associated TPM PCR value. +func (r *pcrResults) Err() error { + if r.err != nil { + // Return the first explicitly set error. + return r.err + } + if len(r.pcrValue) == 0 { + return errors.New("PCR value has not been obtained from TPM yet") + } + if bytes.Equal(r.pcrValue, r.initialValue) { + // Return an error if the PCR hasn't been extended. + return errors.New("PCR has not been extended by platform firmware") + } + if !bytes.Equal(r.pcrValue, r.logValue) { + // The PCR value is inconsistent with the log value. + return fmt.Errorf("PCR value mismatch (actual from TPM %#x, reconstructed from log %#x)", r.pcrValue, r.logValue) + } + return nil +} + +// setInitialValue sets the initial value for the associated PCR in the associated bank. +// This will panic if extend has already been called, so the caller should check this. It +// will also panic if the supplied digest has the wrong size +func (r *pcrResults) setInitialValue(digest tpm2.Digest) { + if !bytes.Equal(r.initialValue, r.logValue) { + panic("cannot set initial log value for PCR once extend has been called") + } + if len(digest) != len(r.initialValue) { + panic("invalid initial digest length") + } + copy(r.initialValue, digest) + copy(r.logValue, digest) +} + +// extend performs a hash extend of the logValue field with the supplied digest using the +// specified algorithm. This will return an error if the supplied digest has the wrong +// length. +func (r *pcrResults) extend(alg tpm2.HashAlgorithmId, digest tpm2.Digest) error { + if alg.Size() != len(r.logValue) { + panic("invalid algorithm supplied") + } + if alg.Size() != len(digest) { + // Don't panic here because digest comes from the log and perhaps the log + // defines the wrong size for the algorithm + return errors.New("invalid digest length") + } + h := alg.NewHash() + h.Write(r.logValue) + h.Write(digest) + r.logValue = h.Sum(nil) + return nil +} + +// setPcrValue records the PCR value from the TPM. This will panic if it's called more than +// once. It will return an error if the value length doesn't match the log value length. +func (r *pcrResults) setPcrValue(value tpm2.Digest) error { + if len(r.pcrValue) > 0 { + panic("cannot set PCR value more than once") + } + if len(value) != len(r.logValue) { + // Don't panic here because the digest is returned directly + // from the TPM + return errors.New("invalid digest length") + } + r.pcrValue = value + return nil +} + +// setErr sets an error for the associated PCR in the associated bank. This doesn't +// overwrite previously set errors (ie, [Error] will return the first set error). +func (r *pcrResults) setErr(err error) { + if r.err != nil { + return + } + r.err = err +} + +// pcrBankResults represents the result of reconstructing the log for an entire +// PCR bank. +type pcrBankResults struct { + Alg tpm2.HashAlgorithmId // the digest algorithm of the PCR bank + StartupLocality uint8 // the startup locality + pcrs [8]pcrResults // individual PCR results +} + +func newPcrBankResults(alg tpm2.HashAlgorithmId, mandatoryPcrs tpm2.HandleList) (out *pcrBankResults) { + out = &pcrBankResults{ + Alg: alg, + StartupLocality: 0, + } + for pcr := range out.pcrs { + result := out.Lookup(tpm2.Handle(pcr)) + for _, mandatoryPcr := range mandatoryPcrs { + if mandatoryPcr == tpm2.Handle(pcr) { + result.mandatory = true + break + } + } + result.initialValue = make(tpm2.Digest, alg.Size()) + result.logValue = make(tpm2.Digest, alg.Size()) + // leave the pcrValue field uninitialized for now + } + return out +} + +// Ok indicates that the log was reconstructed ok for all mandatory PCRs for the associated PCR bank. +func (r *pcrBankResults) Ok() bool { + for _, result := range r.pcrs { + if !result.Ok() && result.mandatory { + return false + } + } + return true +} + +// Lookup looks up the result for the specified PCR. This will panic if the supplied +// PCR is out of range (ie, not 0-7). +func (r *pcrBankResults) Lookup(pcr tpm2.Handle) *pcrResults { + if !internal_efi.IsTCGDefinedPCR(pcr) { + panic("invalid PCR index") + } + return &r.pcrs[pcr] +} + +// extend performs a hash extend of the logValue field of the pcrResult struct +// associated with the specified pcr, using the supplied digest. It will panic +// if the specified PCR is out of range. It will return an error if the supplied +// digest has the wrong size. +func (r *pcrBankResults) extend(pcr tpm2.Handle, digest tpm2.Digest) error { + return r.Lookup(pcr).extend(r.Alg, digest) +} + +// setPcrValues records the PCR values obtained from the TPM. It will panic +// if this is called more than once. It will return an error if any value returned +// from the TPM has the wrong size. +func (r *pcrBankResults) setPcrValues(values tpm2.PCRValues) error { + for pcr, digest := range values[r.Alg] { + if !internal_efi.IsTCGDefinedPCR(tpm2.Handle(pcr)) { + continue + } + if err := r.Lookup(tpm2.Handle(pcr)).setPcrValue(digest); err != nil { + return fmt.Errorf("cannot record value for PCR %d: %w", pcr, err) + } + } + return nil +} + +// pcrErrs returns a map of PCRs to errors for individual PCRs associated with this bank. +func (r *pcrBankResults) pcrErrs() (out map[tpm2.Handle]error) { + out = make(map[tpm2.Handle]error) + for pcr, result := range r.pcrs { + err := result.Err() + if err == nil { + continue + } + out[tpm2.Handle(pcr)] = err + } + return out +} + +// checkFirmwareLogAgainstTPMForAlg checks the supplied TCG log consistency, reconstructed against the +// TPM PCRs for the specified algorithm. This only checks TCG defined PCRs (0-7). +func checkFirmwareLogAgainstTPMForAlg(tpm *tpm2.TPMContext, log *tcglog.Log, alg tpm2.HashAlgorithmId, mandatoryPcrs tpm2.HandleList) (results *pcrBankResults, err error) { + // Check that the TCG log contains the specified algorithm + supported := false + for _, logAlg := range log.Algorithms { + if logAlg != alg { + continue + } + // It does, so that's a good start. + supported = true + break + } + if !supported { + // The log doesn't contain the specified algorithm + return nil, errors.New("digest algorithm not present in log") + } + + // Create the result tracker for PCRs 0-7 + results = newPcrBankResults(alg, mandatoryPcrs) + + seenStartupLocalityEvent := false + + // Iterate over the log + for i, ev := range log.Events { + if !internal_efi.IsTCGDefinedPCR(ev.PCRIndex) { + // Skip all events that aren't defined by the TCG + continue + } + + if ev.EventType == tcglog.EventTypeNoAction { + // EV_NO_ACTION events are informational and not measured, so ignore most of them with the exception + // of StartupLocality events, which affects the initial value of PCR0. + startupLocalityData, isStartupLocality := ev.Data.(*tcglog.StartupLocalityEventData) + if !isStartupLocality { + // Not a StartupLocality event. + continue + } + + // This is the StartupLocality event which is added to the log to indicate + // which locality the TPM2_Startup command was executed from. The reference + // TPM implementation only allows this to be executed from localities 0 and 3. + // Executing it from locality 3, which is generally unavailable to the host CPU, + // changes the reset value of PCR 0 (the least significant 2 bits are set). + // PCR0 can also be initialized before TPM2_Startup by a H-CRTM event sequence from + // locality 4, which will be reflected in this event in this case by setting the + // startup locality to 4, and results in PCR0 being initialized with bit 2 set and + // the least significant 2 bits set to zero, regardless of what locality TPM2_Startup + // is subsequently called from. + if ev.PCRIndex != internal_efi.PlatformFirmwarePCR { + // This event should only ever appear in PCR0 + results.Lookup(ev.PCRIndex).setErr(errors.New("unexpected StartupLocality event (should be in PCR0)")) + continue + } + if !bytes.Equal(results.Lookup(0).logValue, results.Lookup(0).initialValue) { + // This event should not appear in the log after events that have already been measured + results.Lookup(0).setErr(errors.New("unexpected StartupLocality event after measurements already made")) + continue + } + if seenStartupLocalityEvent { + results.Lookup(0).setErr(errors.New("unexpected StartupLocality event - more than one appears in log")) + continue + } + seenStartupLocalityEvent = true + + switch startupLocalityData.StartupLocality { + case 0, 3, 4: + // Valid startup locality values, with 4 meaning there was a H-CRTM event sequence. + results.StartupLocality = startupLocalityData.StartupLocality + digest := make(tpm2.Digest, alg.Size()) + digest[alg.Size()-1] = results.StartupLocality + results.Lookup(0).setInitialValue(digest) + default: + // Invalid startup locality value + results.Lookup(0).setErr(fmt.Errorf("invalid StartupLocality value %d - "+ + "TPM2_Startup is only permitted from locality 0 or 3, "+ + "or PCR0 can be initialized from locality 4 by a H-CRTM event before TPM2_Startup is called", startupLocalityData.StartupLocality)) + results.StartupLocality = 0 + } + continue + } + + // XXX: The TGC PC-Client PFP spec v1.06 is a bit vague wrt EV_S_CRTM_CONTENTS and EV_S_CRTM_VERSION events when there was a H-CRTM + // event sequence. Table 27 says that EV_S_CRTM_CONTENTS and EV_S_CRTM_VERSION events are always measured, but the guidelines for + // measuring to PCR0 separately say: + // - If an H-CRTM measured the SRTM version, log the SRTM version identifier measurement using an EV_S_CRTM_VERSION event. + // - If an H-CRTM measured the SRTM contents, log the SRTM contents measurement(s) using an EV_S_CRTM_CONTENTS event. + // ... which suggests that these events are added to the log just to provide information, like EV_NO_ACTION events, in order + // to accompany the EV_EFI_HCRTM_EVENT events from which would contain the actual measured digests. + // + // I don't know what this looks like in practise because I've never come across a device that uses H-CRTM event sequences, so + // we'll just not do anything special here for now. If this is wrong, we'll probably mis-predict values for PCR0, but we can + // fix this up if/when we come across this sequence in the wild (and perhaps this is unlikely, which might be why this is so + // poorly and inconsistently defined). + + // This is a TCG defined event that is measured, so perform a hash extend + if err := results.extend(ev.PCRIndex, ev.Digests[alg]); err != nil { + // This should only return an error if the spec ID event at the start + // of the log specifies the wrong size for the algorithm, in which case + // all events will be wrong. Best to just bail here rather than set an + // error on just this PCR. + return nil, fmt.Errorf("cannot perform extend with event %d from PCR %d: %w", i, ev.PCRIndex, err) + } + } + + // Read the actual PCR values from the TPM. + pcrs := tpm2.PCRSelectionList{{Hash: alg, Select: []int{0, 1, 2, 3, 4, 5, 6, 7}}} + _, values, err := tpm.PCRRead(pcrs) + if err != nil { + return nil, err + } + + // Record these values on the results + if err := results.setPcrValues(values); err != nil { + return nil, fmt.Errorf("cannot process PCR values from TPM: %w", err) + } + + return results, nil +} + +// checkFirmwareLogAndChoosePCRBank verifies that the firmware TCG log is in crypto-agile form and +// consistent with at least one supported PCR bank when reconstructed for the TCG defined PCRs (0-7). It +// also ensures that: +// - the TCG defined PCRs contain a EV_SEPARATOR event between the pre-OS and OS-present environment ( +// although the one in PCR7 separates secure boot policy from secure boot authentication). +// - that none of the EV_SEPARATORs in the TCG defined PCRs indicated that an error occurred. +// - there are no pre-OS measurements to non-TCG defined PCRs (8-). +// +// This won't return an error for failures in TCG defined PCRs if they aren't part of the specified mandatory +// PCRs set, but the errors will be accessible on the returned results struct. +// +// The returned results struct indicates the best PCR bank to use and specifies the TPM startup locality as well. +func checkFirmwareLogAndChoosePCRBank(tpm *tpm2.TPMContext, log *tcglog.Log, mandatoryPcrs tpm2.HandleList) (results *pcrBankResults, err error) { + // Make sure it's a crypto-agile log + if !log.Spec.IsEFI_2() { + return nil, errors.New("invalid log spec") + } + + // Chose the best PCR bank, ordered from SHA-512, SHA-384 to SHA-256. We're most + // likely to get SHA-256 here - it's only in very recent devices that we have TPMs with + // SHA-384 support and corresponding firmware integration. + // We try to keep all errors enountered during selection here. + mainErr := new(NoSuitablePCRAlgorithmError) + var chosenResults *pcrBankResults + for _, alg := range supportedAlgs { + if chosenResults != nil { + // We've already got a good PCR bank, so no need to carry on. + break + } + + results, err := checkFirmwareLogAgainstTPMForAlg(tpm, log, alg, mandatoryPcrs) + switch { + case err != nil: + // This entire bank is bad + mainErr.setBankErr(alg, err) + case results.Ok(): + // This is a good PCR bank + chosenResults = results + default: + // This isn't a good PCR bank because some mandatory PCRs + // failed. Record the individual PCR errors. + mainErr.setPcrErrs(results) + } + } + + if chosenResults == nil { + // No suitable PCR bank was found, so return an error that's hopefully useful :( + return nil, mainErr + } + + // Make sure we have EV_SEPARATORS for PCRs 0-7 and that no events are measured from the pre-OS + // environment to PCR8 and beyond + var seen int + for _, ev := range log.Events { + if !internal_efi.IsTCGDefinedPCR(ev.PCRIndex) { + return nil, fmt.Errorf("measurements were made by firmware from pre-OS environment to non-TCG defined PCR %d", ev.PCRIndex) + } + if ev.EventType != tcglog.EventTypeSeparator { + continue + } + seen += 1 // we've seen another separator + data, ok := ev.Data.(*tcglog.SeparatorEventData) + if !ok { + // if it failed to decode then it's guaranteed to implement the error interface. + return nil, fmt.Errorf("invalid event data for separator in PCR %d: %w", ev.PCRIndex, ev.Data.(error)) + } + if data.IsError() { + // The EV_SEPARATOR event indicates that an error occurred. + return nil, fmt.Errorf("error separator for PCR %d (error code in log: %d)", ev.PCRIndex, binary.LittleEndian.Uint32(data.Bytes())) + } + if seen >= 8 { + // we've seen separators for all TCG defined PCRs, so this is the point of the pre-OS to OS-present + // transition. None of the separators indicated an error occurred and there were no measurement from + // the pre-OS environment to PCRs 8-. + break + } + } + + if seen != 8 { + return nil, errors.New("reached the end of the log without seeing EV_SEPARATOR events in all TCG defined PCRs") + } + + // At this point, we've selected a PCR bank where the TCG log is consistent with the PCR values for + // mandatory PCRs, and the log is generally in good order, although there are more detailed PCR-specific + // tests to perform later on as well. + return chosenResults, nil +} diff --git a/efi/preinstall/check_tcglog_test.go b/efi/preinstall/check_tcglog_test.go new file mode 100644 index 00000000..b1118cd0 --- /dev/null +++ b/efi/preinstall/check_tcglog_test.go @@ -0,0 +1,729 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package preinstall_test + +import ( + "errors" + "io" + + "github.com/canonical/go-tpm2" + tpm2_testutil "github.com/canonical/go-tpm2/testutil" + "github.com/canonical/tcglog-parser" + . "github.com/snapcore/secboot/efi/preinstall" + internal_efi "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/efitest" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type tcglogSuite struct { + tpm2_testutil.TPMSimulatorTest +} + +var _ = Suite(&tcglogSuite{}) + +func (s *tcglogSuite) resetTPMAndReplayLog(c *C, log *tcglog.Log, algs ...tpm2.HashAlgorithmId) { + s.ResetTPMSimulatorNoStartup(c) // Shutdown and reset the simulator to reset the PCRs back to their reset values. + // Don't immediately call TPM2_Startup in case the log indicates we need to change localities. + started := false + for _, ev := range log.Events { + if ev.EventType == tcglog.EventTypeNoAction { + // EV_NO_ACTION events are informational and not measured + if startupLocalityData, isStartupLocality := ev.Data.(*tcglog.StartupLocalityEventData); isStartupLocality { + c.Assert(ev.PCRIndex, Equals, internal_efi.PlatformFirmwarePCR) + c.Assert(started, testutil.IsFalse) + + switch startupLocalityData.StartupLocality { + case 0: + // do nothing + case 3: + s.Mssim(c).SetLocality(3) + c.Assert(s.TPM.Startup(tpm2.StartupClear), IsNil) + s.Mssim(c).SetLocality(0) + started = true + default: + c.Fatal("TPM2_Startup can only be called from localities 0 or 3") + } + } + continue + } + + if !started { + // Our first actual measurement and we haven't called TPM2_Startup yet + c.Assert(s.TPM.Startup(tpm2.StartupClear), IsNil) + started = true + } + + var digests tpm2.TaggedHashList + for _, alg := range algs { + digest, ok := ev.Digests[alg] + c.Assert(ok, testutil.IsTrue) + digests = append(digests, tpm2.MakeTaggedHash(alg, tpm2.Digest(digest))) + } + c.Assert(s.TPM.PCRExtend(s.TPM.PCRHandleContext(int(ev.PCRIndex)), digests, nil), IsNil) + } +} + +func (s *tcglogSuite) allocatePCRBanks(c *C, algs ...tpm2.HashAlgorithmId) { + current, err := s.TPM.GetCapabilityPCRs() + c.Assert(err, IsNil) + + pcrs := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23} + + // Iterate over the returned PCR selections first + for i, selection := range current { + found := false + for _, alg := range algs { + if selection.Hash == alg { + found = true // An algorithm we want to enable already appears in the TPML_PCR_SELECTION + break + } + } + switch found { + case true: + // Enable this bank + current[i].Select = pcrs + case false: + current[i].Select = nil // Disable this bank by clearing the selection + } + } + + // We might need to enable PCR banks by adding extra TPMS_PCR_SELECTION structures + for _, alg := range algs { + found := false + for _, selection := range current { + if selection.Hash == alg { + // We enabled this one earlier + found = true + break + } + } + if found { + continue + } + // We haven't enabled this one yet, so append a new TPMS_PCR_SELECTION + current = append(current, tpm2.PCRSelection{Hash: alg, Select: pcrs}) + } + + // Set the PCR allocation + success, _, _, _, err := s.TPM.PCRAllocate(s.TPM.PlatformHandleContext(), current, nil) + c.Assert(err, IsNil) + c.Assert(success, testutil.IsTrue) + + s.ResetTPMSimulator(c) +} + +type testCheckFirmwareLogAndChoosePCRBankParams struct { + enabledBanks []tpm2.HashAlgorithmId + logAlgs []tpm2.HashAlgorithmId + startupLocality uint8 + replayAlgs []tpm2.HashAlgorithmId + mandatoryPcrs tpm2.HandleList + + expectedAlg tpm2.HashAlgorithmId +} + +func (s *tcglogSuite) testCheckFirmwareLogAndChoosePCRBank(c *C, params *testCheckFirmwareLogAndChoosePCRBankParams) { + s.allocatePCRBanks(c, params.enabledBanks...) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: params.logAlgs, + StartupLocality: params.startupLocality, + }) + s.resetTPMAndReplayLog(c, log, params.replayAlgs...) + result, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, params.mandatoryPcrs) + c.Assert(err, IsNil) + c.Check(result.Alg, Equals, params.expectedAlg) + c.Check(result.StartupLocality, Equals, params.startupLocality) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA256(c *C) { + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA256, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA384(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA384}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA384}, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA384}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA384, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA512(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA512) + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA512}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA512}, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA512}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA512, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankMultipleSHA384(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA384, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA256WithEmptySHA384Bank(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA256, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA256StartupLocality3(c *C) { + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + startupLocality: 3, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA256, + }) +} + +// TODO(chrisccoulson): github.com/canonical/go-tpm2/mssim needs support for sending H-CRTM +// event sequences to the simulator in order to run this test, which is relatively non-trivial +// to add - see https://github.com/canonical/go-tpm2/issues/18 +//func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSHA256WithHCRTM(c *C) { +// s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ +// enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, +// logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, +// startupLocality: 4, +// replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, +// mandatoryPcrs: tpm2.HandleList{ +// internal_efi.PlatformFirmwarePCR, +// internal_efi.PlatformFirmwareConfigPCR, +// internal_efi.DriversAndAppsPCR, +// internal_efi.DriversAndAppsConfigPCR, +// internal_efi.BootManagerCodePCR, +// internal_efi.BootManagerCodeConfigPCR, +// internal_efi.PlatformManufacturerPCR, +// internal_efi.SecureBootPolicyPCR, +// }, +// expectedAlg: tpm2.HashAlgorithmSHA256, +// }) +//} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankMultipleSHA384StartupLocality3(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.testCheckFirmwareLogAndChoosePCRBank(c, &testCheckFirmwareLogAndChoosePCRBankParams{ + enabledBanks: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + logAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + startupLocality: 3, + replayAlgs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + mandatoryPcrs: tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + expectedAlg: tpm2.HashAlgorithmSHA384, + }) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankUnexpectedStartupLocality(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + // Move the startup locality event to PCR 1 + + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.PlatformFirmwarePCR { + continue + } + if ev.EventType != tcglog.EventTypeNoAction { + continue + } + if _, isStartupLocality := ev.Data.(*tcglog.StartupLocalityEventData); !isStartupLocality { + continue + } + + ev.PCRIndex = internal_efi.PlatformFirmwareConfigPCR + break + } + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + }) + c.Check(err, ErrorMatches, `no suitable PCR algorithm available: +- TPM_ALG_SHA512: digest algorithm not present in log. +- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA256\(PCR0\): PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\). +- TPM_ALG_SHA256\(PCR1\): unexpected StartupLocality event \(should be in PCR0\). +`) + var e *NoSuitablePCRAlgorithmError + c.Check(errors.As(err, &e), testutil.IsTrue) + + // Test that we can access individual errors. + c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA512), ErrorMatches, `digest algorithm not present in log`) + c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA384), ErrorMatches, `digest algorithm not present in log`) + c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA384, internal_efi.PlatformFirmwarePCR), IsNil) + c.Check(e.UnwrapBankError(tpm2.HashAlgorithmSHA256), IsNil) + c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.PlatformFirmwarePCR), ErrorMatches, `PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\)`) + c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.PlatformFirmwareConfigPCR), ErrorMatches, `unexpected StartupLocality event \(should be in PCR0\)`) + c.Check(e.UnwrapPCRError(tpm2.HashAlgorithmSHA256, internal_efi.DriversAndAppsPCR), IsNil) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankOutOfPlaceStartupLocality(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + // Move the startup locality event after the first EV_NO_ACTION event in PCR 0 + var slEvent *tcglog.Event // the startup locality event + events := log.Events // the current events + var eventsCopy []*tcglog.Event // a copy of the events + + // Find the startup locality event, omitting it from the copy of events + for len(events) > 0 { + ev := events[0] + events = events[1:] + eventsCopy = append(eventsCopy, ev) + + if ev.PCRIndex != internal_efi.PlatformFirmwarePCR { + continue + } + if ev.EventType != tcglog.EventTypeNoAction { + continue + } + if _, isStartupLocality := ev.Data.(*tcglog.StartupLocalityEventData); !isStartupLocality { + continue + } + + slEvent = ev + eventsCopy = eventsCopy[:len(eventsCopy)-1] // truncate the copy of events by 1 + break + } + + c.Assert(slEvent, NotNil) + + // Find the first non EV_NO_ACTION event in PCR 0 and move the startup locality event after it + for len(events) > 0 { + ev := events[0] + events = events[1:] + eventsCopy = append(eventsCopy, ev) + + if ev.PCRIndex == internal_efi.PlatformFirmwarePCR && + ev.EventType != tcglog.EventTypeNoAction && slEvent != nil { + eventsCopy = append(eventsCopy, slEvent) + slEvent = nil + } + } + + // Swap the log over + log.Events = eventsCopy + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + }) + c.Check(err, ErrorMatches, `no suitable PCR algorithm available: +- TPM_ALG_SHA512: digest algorithm not present in log. +- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA256\(PCR0\): unexpected StartupLocality event after measurements already made. +`) + var e *NoSuitablePCRAlgorithmError + c.Check(errors.As(err, &e), testutil.IsTrue) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankInvalidStartupLocality(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + // Change the startup locality to 2 + + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex != internal_efi.PlatformFirmwarePCR { + continue + } + if ev.EventType != tcglog.EventTypeNoAction { + continue + } + if _, isStartupLocality := ev.Data.(*tcglog.StartupLocalityEventData); !isStartupLocality { + continue + } + + ev.Data = &tcglog.StartupLocalityEventData{StartupLocality: 2} + break + } + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + }) + c.Check(err, ErrorMatches, `no suitable PCR algorithm available: +- TPM_ALG_SHA512: digest algorithm not present in log. +- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA256\(PCR0\): invalid StartupLocality value 2 - TPM2_Startup is only permitted from locality 0 or 3, or PCR0 can be initialized from locality 4 by a H-CRTM event before TPM2_Startup is called. +`) + var e *NoSuitablePCRAlgorithmError + c.Check(errors.As(err, &e), testutil.IsTrue) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPCRMismatchMandatory(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + // This will make the PCR 0 calculation wrong + log = efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + }) + c.Check(err, ErrorMatches, `no suitable PCR algorithm available: +- TPM_ALG_SHA512: digest algorithm not present in log. +- TPM_ALG_SHA384: digest algorithm not present in log. +- TPM_ALG_SHA256\(PCR0\): PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\). +`) + var e *NoSuitablePCRAlgorithmError + c.Check(errors.As(err, &e), testutil.IsTrue) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPCRMismatchNonMandatory(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + // This will make the PCR 0 calculation wrong + log = efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + results, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, + tpm2.HandleList{ + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + ) + c.Assert(err, IsNil) + c.Check(results.Alg, Equals, tpm2.HashAlgorithmSHA256) + c.Check(results.Ok(), Equals, true) + c.Check(results.Lookup(internal_efi.PlatformFirmwarePCR).Ok(), Equals, false) + c.Check(results.Lookup(internal_efi.PlatformFirmwarePCR).Err(), ErrorMatches, `PCR value mismatch \(actual from TPM 0xb0d6d5f50852be1524306ad88b928605c14338e56a1b8c0dc211a144524df2ef, reconstructed from log 0xa6602a7a403068b5556e78cc3f5b00c9c76d33d514093ca9b584dce7590e6c69\)`) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPCRMismatchMandatoryInOneBank(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384) + + for i, ev := range log.Events { + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + continue + } + if ev.EventType == tcglog.EventTypeEFIAction { + log.Events[i].Digests[tpm2.HashAlgorithmSHA384] = make(tpm2.Digest, tpm2.HashAlgorithmSHA384.Size()) + } + } + + results, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, + tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodePCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + ) + c.Assert(err, IsNil) + c.Check(results.Alg, Equals, tpm2.HashAlgorithmSHA256) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPCRMismatchNonMandatoryInOneBank(c *C) { + s.RequireAlgorithm(c, tpm2.AlgorithmSHA384) + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384}, + StartupLocality: 3, + }) + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA384) + + for i, ev := range log.Events { + if ev.PCRIndex != internal_efi.BootManagerCodePCR { + continue + } + if ev.EventType == tcglog.EventTypeEFIAction { + log.Events[i].Digests[tpm2.HashAlgorithmSHA384] = make(tpm2.Digest, tpm2.HashAlgorithmSHA384.Size()) + } + } + + results, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, + tpm2.HandleList{ + internal_efi.PlatformFirmwarePCR, + internal_efi.PlatformFirmwareConfigPCR, + internal_efi.DriversAndAppsPCR, + internal_efi.DriversAndAppsConfigPCR, + internal_efi.BootManagerCodeConfigPCR, + internal_efi.PlatformManufacturerPCR, + internal_efi.SecureBootPolicyPCR, + }, + ) + c.Assert(err, IsNil) + c.Check(results.Alg, Equals, tpm2.HashAlgorithmSHA384) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankBadSpec(c *C) { + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + log.Spec = tcglog.Spec{ + PlatformType: tcglog.PlatformTypeEFI, + Major: 1, + Minor: 2, + Errata: 0, + } + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) + c.Check(err, ErrorMatches, `invalid log spec`) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankPreOSMeasurementToNonTCGPCR(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + + var eventsCopy []*tcglog.Event + events := log.Events + added := false + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.PCRIndex >= internal_efi.PlatformFirmwarePCR && ev.PCRIndex <= internal_efi.PlatformManufacturerPCR && ev.EventType == tcglog.EventTypeSeparator && !added { + eventsCopy = append(eventsCopy, &tcglog.Event{ + PCRIndex: 8, + EventType: tcglog.EventTypeEventTag, + Data: &tcglog.TaggedEvent{EventID: 10, Data: []byte{1, 2, 3, 4}}, + Digests: tcglog.DigestMap{tpm2.HashAlgorithmSHA256: make([]byte, 32)}, + }) + added = true + } + + eventsCopy = append(eventsCopy, ev) + } + log.Events = eventsCopy + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) + c.Check(err, ErrorMatches, `measurements were made by firmware from pre-OS environment to non-TCG defined PCR 8`) +} + +type invalidEventData struct { + err error +} + +func (e *invalidEventData) String() string { return "invalid event data: " + e.err.Error() } +func (*invalidEventData) Bytes() []byte { return nil } +func (*invalidEventData) Write(w io.Writer) error { return errors.New("not supported") } +func (e *invalidEventData) Error() string { return e.err.Error() } + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSeparatorDecodeError(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.EventType != tcglog.EventTypeSeparator { + continue + } + + ev.Data = &invalidEventData{errors.New("some error")} + break + } + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) + c.Check(err, ErrorMatches, `invalid event data for separator in PCR 7: some error`) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankSeparatorError(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.EventType != tcglog.EventTypeSeparator { + continue + } + + ev.Data = &tcglog.SeparatorEventData{Value: tcglog.SeparatorEventErrorValue, ErrorInfo: []byte{1, 2, 3, 4}} + break + } + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) + c.Check(err, ErrorMatches, `error separator for PCR 7 \(error code in log: 67305985\)`) +} + +func (s *tcglogSuite) TestCheckFirmwareLogAndChoosePCRBankMissingSeparators(c *C) { + s.allocatePCRBanks(c, tpm2.HashAlgorithmSHA256) + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}, + }) + var eventsCopy []*tcglog.Event + skippedOneSeparator := false + events := log.Events + for len(events) > 0 { + ev := events[0] + events = events[1:] + + if ev.EventType != tcglog.EventTypeSeparator || skippedOneSeparator { + eventsCopy = append(eventsCopy, ev) + continue + } + + skippedOneSeparator = true + } + log.Events = eventsCopy + s.resetTPMAndReplayLog(c, log, tpm2.HashAlgorithmSHA256) + + _, err := CheckFirmwareLogAndChoosePCRBank(s.TPM, log, nil) + c.Check(err, ErrorMatches, `reached the end of the log without seeing EV_SEPARATOR events in all TCG defined PCRs`) +} diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go index 0b8b28fa..f0607077 100644 --- a/efi/preinstall/export_test.go +++ b/efi/preinstall/export_test.go @@ -45,6 +45,7 @@ const ( var ( CalculateIntelMEFamily = calculateIntelMEFamily CheckCPUDebuggingLockedMSR = checkCPUDebuggingLockedMSR + CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank CheckForKernelIOMMU = checkForKernelIOMMU CheckPlatformFirmwareProtections = checkPlatformFirmwareProtections CheckPlatformFirmwareProtectionsIntelMEI = checkPlatformFirmwareProtectionsIntelMEI diff --git a/internal/efi/tcg_pcrs.go b/internal/efi/tcg_pcrs.go index 131ffa95..da2aa804 100644 --- a/internal/efi/tcg_pcrs.go +++ b/internal/efi/tcg_pcrs.go @@ -26,14 +26,28 @@ const ( PlatformFirmwarePCR tpm2.Handle = 0 // HostPlatformConfigPCR is the Host Platform Configuration PCR - HostPlatformConfigPCR tpm2.Handle = 1 + PlatformFirmwareConfigPCR tpm2.Handle = 1 // DriversAndAppsPCR is the UEFI Drivers and UEFI Applications PCR DriversAndAppsPCR tpm2.Handle = 2 + // DriversAndAppsConfigPCR is the UEFI driver and application Configuration and Data PCR + DriversAndAppsConfigPCR tpm2.Handle = 3 + // BootManagerCodePCR is the Boot Manager Code and Boot Attempts PCR BootManagerCodePCR tpm2.Handle = 4 + // BootManagerCodeConfigPCR is the Boot Manager Code Configuration and Data + // (for use by the Boot Manager Code) and GPT/Partition Table PCR. + BootManagerCodeConfigPCR tpm2.Handle = 5 + + // PlatformManufacturerPCR is the Host Platform Manufacturer Specific PCR + PlatformManufacturerPCR tpm2.Handle = 6 + // SecureBootPolicyPCR is the Secure Boot Policy Measurements PCR SecureBootPolicyPCR tpm2.Handle = 7 ) + +func IsTCGDefinedPCR(pcr tpm2.Handle) bool { + return pcr <= SecureBootPolicyPCR +} diff --git a/internal/efitest/log.go b/internal/efitest/log.go index 66c6d66f..d58ce3f7 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -100,21 +100,21 @@ const ( type LogOptions struct { Algorithms []tpm2.HashAlgorithmId // the digest algorithms to include + StartupLocality uint8 // specify a startup locality other than 0 FirmwareDebugger bool // indicate a firmware debugger endpoint is enabled DMAProtectionDisabled DMAProtectionDisabledEventType // whether DMA protection is disabled - SecureBootDisabled bool - IncludeDriverLaunch bool // include a driver launch in the log - IncludeSysPrepAppLaunch bool // include a system-preparation app launch in the log - IncludeOSPresentFirmwareAppLaunch efi.GUID // include a flash based application launch in the log as part of the OS-present phase - NoCallingEFIApplicationEvent bool // omit the EV_EFI_ACTION "Calling EFI Application from Boot Option" event. - NoSBAT bool // omit the SbatLevel measurement. - StartupLocality uint8 // specify a startup locality other than 0 + SecureBootDisabled bool // Whether secure boot is disabled + IncludeDriverLaunch bool // include a driver launch from a PCI device in the log + IncludeSysPrepAppLaunch bool // include a system-preparation app launch in the log + NoCallingEFIApplicationEvent bool // omit the EV_EFI_ACTION "Calling EFI Application from Boot Option" event. + IncludeOSPresentFirmwareAppLaunch efi.GUID // include a flash based application launch in the log as part of the OS-present phase + NoSBAT bool // omit the SbatLevel measurement to mimic older versions of shim } // NewLog creates a mock TCG log for testing. The log will look like a standard // Linux boot (shim -> grub -> kernel) and uses hard-coded values for signature -// databases and launch digests. The supplied options argument can be used for -// minimal customization. +// databases (Microsoft UEFI CA 2011 configuration, and launch digests. The +// supplied options argument can be used for minimal customization. func NewLog(c *C, opts *LogOptions) *tcglog.Log { builder := &logBuilder{algs: opts.Algorithms} @@ -139,6 +139,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { }, }, } + if opts.StartupLocality > 0 { ev := &tcglog.Event{ PCRIndex: 0, @@ -153,32 +154,67 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { } // Mock S-CRTM measurements - { - data := tcglog.StringEventData("1.0") - builder.hashLogExtendEvent(c, data, &logEvent{ + if opts.StartupLocality == 4 { + ev := &tcglog.Event{ + PCRIndex: 0, + EventType: tcglog.EventTypeNoAction, + Digests: make(tcglog.DigestMap), + Data: &tcglog.HCRTMComponentEventData{ + ComponentDescription: "S-CRTM contents", + MeasurementFormatType: tcglog.HCRTMMeasurementFormatRawData, + ComponentMeasurement: []byte("mock S-CRTM contents"), + }, + } + for _, alg := range opts.Algorithms { + ev.Digests[alg] = make(tpm2.Digest, alg.Size()) + } + builder.events = append(builder.events, ev) + + blob := bytesHashData("mock S-CRTM contents") + builder.hashLogExtendEvent(c, blob, &logEvent{ pcrIndex: 0, - eventType: tcglog.EventTypeSCRTMVersion, - data: data}) + eventType: tcglog.EventTypeEFIHCRTMEvent, + data: tcglog.StringEventData("HCRTM")}) + } else { + { + blob := bytesHashData("mock S-CRTM contents") + data := &tcglog.EFIPlatformFirmwareBlob{ + BlobBase: 0xff000000, + BlobLength: 25431} + builder.hashLogExtendEvent(c, blob, &logEvent{ + pcrIndex: 0, + eventType: tcglog.EventTypeSCRTMContents, + data: data}) + } + { + data := tcglog.GUIDEventData(efi.MakeGUID(0x8beb77ea, 0x5c75, 0x4d08, 0x8e2b, [...]byte{0x96, 0x34, 0x86, 0xda, 0xe7, 0xf7})) + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 0, + eventType: tcglog.EventTypeSCRTMVersion, + data: data}) + } } { blob := bytesHashData("mock platform firmware blob 1") - var data [16]byte - binary.LittleEndian.PutUint64(data[0:], 0x820000) - binary.LittleEndian.PutUint64(data[8:], 0xe0000) + data := &tcglog.EFIPlatformFirmwareBlob{ + BlobBase: 0xffc00000, + BlobLength: 0xe0000, + } builder.hashLogExtendEvent(c, blob, &logEvent{ pcrIndex: 0, eventType: tcglog.EventTypeEFIPlatformFirmwareBlob, - data: tcglog.OpaqueEventData(data[:])}) + data: data}) } { blob := bytesHashData("mock platform firmware blob 2") - var data [16]byte - binary.LittleEndian.PutUint64(data[0:], 0x900000) - binary.LittleEndian.PutUint64(data[8:], 0xc00000) + data := &tcglog.EFIPlatformFirmwareBlob{ + BlobBase: 0xffce0000, + BlobLength: 0xc00000, + } builder.hashLogExtendEvent(c, blob, &logEvent{ pcrIndex: 0, eventType: tcglog.EventTypeEFIPlatformFirmwareBlob, - data: tcglog.OpaqueEventData(data[:])}) + data: data}) } sbVal := []byte{0x01} @@ -279,8 +315,22 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { LocationInMemory: 0x41a2f024, LengthInMemory: 659024, DevicePath: efi.DevicePath{ - efi.MediaFvDevicePathNode(efi.MakeGUID(0x9b56c9db, 0x1936, 0x44a9, 0x9ed4, [...]uint8{0xb9, 0x2a, 0xef, 0xfc, 0xbb, 0x66})), - efi.MediaFvFileDevicePathNode(efi.MakeGUID(0x15c7a296, 0xb470, 0x4b31, 0x8314, [...]uint8{0x7f, 0x6e, 0x56, 0x14, 0x37, 0xe5}))}} + &efi.ACPIDevicePathNode{ + HID: 0x0a0341d0, + UID: 0x0, + }, + &efi.PCIDevicePathNode{ + Function: 0x1c, + Device: 0x2, + }, + &efi.PCIDevicePathNode{ + Function: 0x0, + Device: 0x0, + }, + &efi.MediaRelOffsetRangeDevicePathNode{ + StartingOffset: 0x38, + EndingOffset: 0x11dff, + }}} builder.hashLogExtendEvent(c, pe, &logEvent{ pcrIndex: 2, eventType: tcglog.EventTypeEFIBootServicesDriver,