From 691a0fecf8c9ea132be715f58fba552ad3a3a5d0 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Tue, 14 Feb 2023 20:24:43 +0000 Subject: [PATCH 1/4] measure-config : refactoring, don't pre-hash event string sent to PCREvent This commit introduces the following changes: There is no need to hash the event string sent to PCREvent beforehand, it will get hashed internally by all hash algorithms associated with the PCR. PCREvent accepts a maximum of 1024 bytes of event data, to comply break down the event data into chunks of 1024, if the data is larger than 1024 bytes. Signed-off-by: Shahriyar Jalayeri --- pkg/measure-config/src/measurefs.go | 84 +++++++++++++---------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/pkg/measure-config/src/measurefs.go b/pkg/measure-config/src/measurefs.go index 729c0135a4..38ba41981c 100644 --- a/pkg/measure-config/src/measurefs.go +++ b/pkg/measure-config/src/measurefs.go @@ -24,6 +24,8 @@ const ( TpmDevicePath = "/dev/tpmrm0" configPCRIndex = 14 configPCRHandle = tpmutil.Handle(tpm2.PCRFirst + configPCRIndex) + //PCREvent (TPM2_PCR_Event) supports event size of maximum 1024 bytes. + maxEventDataSize = 1024 ) type fileInfo struct { @@ -36,6 +38,13 @@ type tpmEvent struct { pcr []byte } +func min(a, b int) int { + if a < b { + return a + } + return b +} + // we do not measure content of following files // because they are unique for each device func getExcludeList() []string { @@ -84,30 +93,31 @@ func sha256sumForFile(filePath string) (string, error) { return hex.EncodeToString(hash.Sum(nil)), nil } -func sha256sumString(s string) [32]byte { - return sha256.Sum256([]byte(s)) -} - -func measureFileContent(filePath string, tpm io.ReadWriter) (*tpmEvent, error) { - hash, err := sha256sumForFile(filePath) - - if err != nil { - return nil, fmt.Errorf("cannot measure %s :%v", filePath, err) - } - - eventData := fmt.Sprintf("file:%s hash:%s", filePath, hash) - - // it seems PCRExtend expects a hash not data itself. - eventDataHash := sha256sumString(eventData) - - err = tpm2.PCREvent(tpm, configPCRHandle, eventDataHash[:]) - - if err != nil { - return nil, fmt.Errorf("cannot measure %s. couldn't extend PCR: %v", filePath, err) +func performMeasurement(filePath string, tpm io.ReadWriter, exist bool, content bool) (*tpmEvent, error) { + var eventData string + if content { + hash, err := sha256sumForFile(filePath) + if err != nil { + return nil, fmt.Errorf("cannot measure %s :%v", filePath, err) + } + eventData = fmt.Sprintf("file:%s exist:true content-hash:%s", filePath, hash) + } else { + eventData = fmt.Sprintf("file:%s exist:%t", filePath, exist) + } + + // Loop over the data and if it is larger than 1024 (max size PCREvent consumes) + // break it into 1024 bytes chunks, otherwise just loop once and pass data to PCREvent. + for offset, length := 0, 0; offset < len(eventData); offset += length { + length = min(maxEventDataSize, len(eventData)-offset) + // PCREvent internally hashes the data with all supported algorithms + // associated with the PCR banks, and extends them all before return. + err := tpm2.PCREvent(tpm, configPCRHandle, []byte(eventData[offset:offset+length])) + if err != nil { + return nil, fmt.Errorf("cannot measure %s. couldn't extend PCR: %v", filePath, err) + } } pcr, err := readConfigPCR(tpm) - if err != nil { return nil, fmt.Errorf("cannot measure %s. couldn't read PCR: %v", filePath, err) } @@ -115,26 +125,6 @@ func measureFileContent(filePath string, tpm io.ReadWriter) (*tpmEvent, error) { return &tpmEvent{eventData, pcr}, nil } -func measureFilePath(filePath string, tpm io.ReadWriter, exist bool) (*tpmEvent, error) { - eventData := fmt.Sprintf("file:%s exist:%t", filePath, exist) - // it seems PCRExtend expects a hash not data itself. - eventDataHash := sha256sumString(eventData) - - err := tpm2.PCREvent(tpm, configPCRHandle, eventDataHash[:]) - - if err != nil { - return nil, fmt.Errorf("cannot measure path %s. couldn't extend PCR: %v", filePath, err) - } - - pcr, err := readConfigPCR(tpm) - - if err != nil { - return nil, fmt.Errorf("cannot measure path %s. couldn't read PCR: %v", filePath, err) - } - - return &tpmEvent{eventData, pcr}, nil -} - func getFileMap() (map[string]fileInfo, error) { files := make(map[string]fileInfo) @@ -201,19 +191,21 @@ func measureConfig(tpm io.ReadWriter) error { if info.exist { if info.measureContent { - event, err = measureFileContent(file, tpm) + event, err = performMeasurement(file, tpm, true, true) } else { - event, err = measureFilePath(file, tpm, true) + event, err = performMeasurement(file, tpm, true, false) } } else { - event, err = measureFilePath(file, tpm, false) + event, err = performMeasurement(file, tpm, false, false) } if err != nil { return fmt.Errorf("cannot measure %s: %v", file, err) } //Now we have a new value of PCR and an event - //TODO: add events to the event log - // for now just print our measurements to boot log + //TODO: add events to the event log, if event data exceeds 1024 bytes, + // make sure to break it into 1024 bytes chunks with added indicators + // (e.g. part n of m) to be able to reconstruct the even data for validation. + // for now we just print our measurements to boot log. log.Printf("%s pcr:%s", event.data, hex.EncodeToString(event.pcr)) } return nil From e269b78755656d6c7b12061fd63c5cf7072222a1 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Fri, 17 Nov 2023 10:02:05 +0000 Subject: [PATCH 2/4] measure-config: one time event measurement Don't loop over event data if is longer that 1024 bytes, truncate it and just do it once. This helps with having one event per measurements. Signed-off-by: Shahriyar Jalayeri --- pkg/measure-config/src/measurefs.go | 38 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/pkg/measure-config/src/measurefs.go b/pkg/measure-config/src/measurefs.go index 38ba41981c..0bc7248d65 100644 --- a/pkg/measure-config/src/measurefs.go +++ b/pkg/measure-config/src/measurefs.go @@ -95,31 +95,35 @@ func sha256sumForFile(filePath string) (string, error) { func performMeasurement(filePath string, tpm io.ReadWriter, exist bool, content bool) (*tpmEvent, error) { var eventData string + + // Max size for PCREvent data is 1024 bytes, truncate the file path if it + // is longer than 512 bytes. + eventFilePath := filePath + if len(filePath) > 512 { + // in this case just get the file name, it maxes out at 255 chars. + eventFilePath = filepath.Base(filePath) + } + if content { hash, err := sha256sumForFile(filePath) if err != nil { - return nil, fmt.Errorf("cannot measure %s :%v", filePath, err) + return nil, fmt.Errorf("can not measure %s :%v", filePath, err) } - eventData = fmt.Sprintf("file:%s exist:true content-hash:%s", filePath, hash) + eventData = fmt.Sprintf("file:%s exist:true content-hash:%s", eventFilePath, hash) } else { - eventData = fmt.Sprintf("file:%s exist:%t", filePath, exist) + eventData = fmt.Sprintf("file:%s exist:%t", eventFilePath, exist) } - // Loop over the data and if it is larger than 1024 (max size PCREvent consumes) - // break it into 1024 bytes chunks, otherwise just loop once and pass data to PCREvent. - for offset, length := 0, 0; offset < len(eventData); offset += length { - length = min(maxEventDataSize, len(eventData)-offset) - // PCREvent internally hashes the data with all supported algorithms - // associated with the PCR banks, and extends them all before return. - err := tpm2.PCREvent(tpm, configPCRHandle, []byte(eventData[offset:offset+length])) - if err != nil { - return nil, fmt.Errorf("cannot measure %s. couldn't extend PCR: %v", filePath, err) - } + // PCREvent internally hashes the data with all supported algorithms + // associated with the PCR banks, and extends them all before return. + err := tpm2.PCREvent(tpm, configPCRHandle, []byte(eventData)) + if err != nil { + return nil, fmt.Errorf("can not measure %s. couldn't extend PCR: %v", filePath, err) } pcr, err := readConfigPCR(tpm) if err != nil { - return nil, fmt.Errorf("cannot measure %s. couldn't read PCR: %v", filePath, err) + return nil, fmt.Errorf("can not measure %s. couldn't read PCR: %v", filePath, err) } return &tpmEvent{eventData, pcr}, nil @@ -177,7 +181,7 @@ func measureConfig(tpm io.ReadWriter) error { files, err := getFileMap() if err != nil { - return fmt.Errorf("cannot get file list: %v", err) + return fmt.Errorf("can not get file list: %v", err) } //get sorted list of files. We must always go the same order @@ -199,7 +203,7 @@ func measureConfig(tpm io.ReadWriter) error { event, err = performMeasurement(file, tpm, false, false) } if err != nil { - return fmt.Errorf("cannot measure %s: %v", file, err) + return fmt.Errorf("can not measure %s: %v", file, err) } //Now we have a new value of PCR and an event //TODO: add events to the event log, if event data exceeds 1024 bytes, @@ -215,7 +219,7 @@ func readConfigPCR(tpm io.ReadWriter) ([]byte, error) { pcr, err := tpm2.ReadPCR(tpm, configPCRIndex, tpm2.AlgSHA256) if err != nil { - return nil, fmt.Errorf("cannot read PCR %d: %v", configPCRIndex, err) + return nil, fmt.Errorf("can not read PCR %d: %v", configPCRIndex, err) } return pcr, nil } From 106f67075f4e3505b39f3fd76161474ab5e36704 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Fri, 17 Nov 2023 13:39:33 +0000 Subject: [PATCH 3/4] evetpm : use tmp0 to backup tpm event logs We are explicitly using tpm0 all over the place, so simplify the code by removing parts that account for multiple tmp existing on the device and just use tpm0 instead. Signed-off-by: Shahriyar Jalayeri --- pkg/pillar/evetpm/tpm.go | 143 +++++++--------------------------- pkg/pillar/evetpm/tpm_test.go | 87 +++++++++------------ 2 files changed, 62 insertions(+), 168 deletions(-) diff --git a/pkg/pillar/evetpm/tpm.go b/pkg/pillar/evetpm/tpm.go index 28a821bf95..abb8c34a15 100644 --- a/pkg/pillar/evetpm/tpm.go +++ b/pkg/pillar/evetpm/tpm.go @@ -16,7 +16,6 @@ import ( "io/ioutil" "math/big" "os" - "path/filepath" "sort" "unsafe" @@ -89,11 +88,7 @@ const ( // measurementLogFile is a kernel exposed variable that contains the // TPM measurements and events log. - measurementLogFile = "binary_bios_measurements" - - // syfsTpmDir is directory that TPMs get mapped on sysfs, and it contains - // measurement logs. - syfsTpmDir = "/hostfs/sys/kernel/security/tpm*" + measurementLogFile = "/hostfs/sys/kernel/security/tpm0/binary_bios_measurements" ) // PCRBank256Status stores info about support for @@ -650,14 +645,14 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro log.Warnf("saving snapshot of sealing PCRs failed: %s", err) } - // Backup the previous pair of logs if any, so at most we have two pairs of - // measurement logs (per available tpm devices). This is needed because if the - // failing devices get connected to the controller and collects the backup key, - // we end up here again and will override the MeasurementLogSealSuccess with - // current measurement log (which is same as the content of MeasurementLogSealFail) - // and lose the ability to diff and diagnose the issue. + // In order to not lose the ability to diff and diagnose the issue, + // first backup the previous pair of logs (if any). This is needed because + // once the failing devices get connected to the controller to fetch the + // backup key, we end up here again and it'll override the MeasurementLogSealSuccess + // file content with current tpm measurement logs (which is same as the + // content of MeasurementLogSealFail). if err := backupCopiedMeasurementLogs(); err != nil { - log.Warnf("collecting previous snapshot of TPM event log failed: %s", err) + log.Warnf("copying previous snapshot of TPM event log failed: %s", err) } // fresh start, remove old copies of measurement logs. @@ -665,7 +660,8 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro log.Warnf("removing old copies of TPM measurement log failed: %s", err) } - // save a copy of the current measurement log + // save a copy of the current measurement log, this is also called + // if unseal fails to have copy when we fail to unlock the vault. if err := copyMeasurementLog(measurementLogSealSuccess); err != nil { log.Warnf("copying current TPM measurement log failed: %s", err) } @@ -850,127 +846,42 @@ func pcrBankSHA256EnabledHelper() bool { return err == nil } -func getLogCopyPath(destination string, tpmIndex int) string { - return fmt.Sprintf("%s-tpm%d", destination, tpmIndex) -} - -func getLogBackupPath(destination string) string { - return fmt.Sprintf("%s-backup", destination) -} - -func getMappedTpmsPath() ([]string, error) { - paths, err := filepath.Glob(syfsTpmDir) - if err != nil { - return nil, fmt.Errorf("failed to enumerate TPM(s) in sysfs: %w", err) - } else if len(paths) == 0 { - return nil, fmt.Errorf("found no TPM in sysfs") - } - - return paths, nil -} - -func countMappedTpms() (int, error) { - paths, err := getMappedTpmsPath() - if err != nil { - return 0, fmt.Errorf("getMappedTpmsPath failed: %w", err) - } - - return len(paths), nil -} - -func getMeasurementLogFiles() ([]string, error) { - paths, err := getMappedTpmsPath() - if err != nil { - return nil, fmt.Errorf("getMappedTpmsPath failed: %w", err) - } +func backupCopiedMeasurementLogs() error { + sealSuccessBackupPath := fmt.Sprintf("%s-backup", measurementLogSealSuccess) + unsealFailBackupPath := fmt.Sprintf("%s-backup", measurementLogUnsealFail) - enumerated := make([]string, 0) - for _, path := range paths { - fullPath := filepath.Join(path, measurementLogFile) - if fileutils.FileExists(nil, fullPath) { - enumerated = append(enumerated, fullPath) + if fileutils.FileExists(nil, measurementLogSealSuccess) { + if err := os.Rename(measurementLogSealSuccess, sealSuccessBackupPath); err != nil { + return fmt.Errorf("failed to backup tpm \"seal success event\" previously copied measurement log: %v", err) } } - return enumerated, nil -} - -func backupCopiedMeasurementLogs() error { - counted, err := countMappedTpms() - if err != nil { - return fmt.Errorf("countMappedTPMs failed: %w", err) - } - - leftToBackup := counted - for i := 0; i < counted; i++ { - sealSuccessPath := getLogCopyPath(measurementLogSealSuccess, i) - unsealFailPath := getLogCopyPath(measurementLogUnsealFail, i) - if fileutils.FileExists(nil, sealSuccessPath) && fileutils.FileExists(nil, unsealFailPath) { - sealSuccessBackupPath := getLogBackupPath(sealSuccessPath) - unsealFailBackupPath := getLogBackupPath(unsealFailPath) - if err := os.Rename(sealSuccessPath, sealSuccessBackupPath); err != nil { - fmt.Fprintf(os.Stderr, "failed to backup tpm%d \"seal success\" previously copied measurement log: %v", i, err) - continue - } - if err := os.Rename(unsealFailPath, unsealFailBackupPath); err != nil { - fmt.Fprintf(os.Stderr, "failed to backup tpm%d \"unseal fail\" previously copied measurement log: %v", i, err) - _ = os.Rename(sealSuccessBackupPath, sealSuccessPath) - continue - } + if fileutils.FileExists(nil, measurementLogUnsealFail) { + if err := os.Rename(measurementLogUnsealFail, unsealFailBackupPath); err != nil { + _ = os.Rename(sealSuccessBackupPath, measurementLogSealSuccess) + return fmt.Errorf("failed to backup tpm \"unseal fail event\" previously copied measurement log: %v", err) } - - leftToBackup-- - } - - if leftToBackup != 0 { - return fmt.Errorf("failed to backup %d number of previously copied TPM measurement logs", leftToBackup) } return nil } func removeCopiedMeasurementLogs() error { - counted, err := countMappedTpms() - if err != nil { - return fmt.Errorf("countMappedTPMs failed: %w", err) - } - - for i := 0; i < counted; i++ { - sealSuccessPath := getLogCopyPath(measurementLogSealSuccess, i) - unsealFailPath := getLogCopyPath(measurementLogUnsealFail, i) - _ = os.Remove(sealSuccessPath) - _ = os.Remove(unsealFailPath) - } + _ = os.Remove(measurementLogSealSuccess) + _ = os.Remove(measurementLogUnsealFail) return nil } func copyMeasurementLog(dstPath string) error { - paths, err := getMeasurementLogFiles() + measurementLogContent, err := os.ReadFile(measurementLogFile) if err != nil { - return fmt.Errorf("enumSourceEventLogFiles failed: %w", err) + return fmt.Errorf("failed to read TPM measurements log file: %v", err) } - leftToCopy := len(paths) - for i, path := range paths { - measurementLogContent, err := os.ReadFile(path) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to read stored measurement log file: %v", err) - continue - } - - copyPath := getLogCopyPath(dstPath, i) - err = fileutils.WriteRename(copyPath, measurementLogContent) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to copy stored measurement log file: %v", err) - continue - } - - leftToCopy-- - } - - if leftToCopy != 0 { - return fmt.Errorf("failed to copy %d number of stored measurement log files", leftToCopy) + err = fileutils.WriteRename(dstPath, measurementLogContent) + if err != nil { + return fmt.Errorf("failed to copy stored measurement log file: %v", err) } return nil diff --git a/pkg/pillar/evetpm/tpm_test.go b/pkg/pillar/evetpm/tpm_test.go index 9c0b51333c..9deb6e6467 100644 --- a/pkg/pillar/evetpm/tpm_test.go +++ b/pkg/pillar/evetpm/tpm_test.go @@ -8,6 +8,7 @@ package evetpm import ( "bytes" "crypto/sha256" + "fmt" "os" "reflect" "strings" @@ -30,16 +31,15 @@ func TestSealUnseal(t *testing.T) { dataToSeal := []byte("secret") if err := SealDiskKey(log, dataToSeal, DiskKeySealingPCRs); err != nil { - t.Errorf("Seal operation failed with err: %v", err) - return + t.Fatalf("Seal operation failed with err: %v", err) } + unsealedData, err := UnsealDiskKey(DiskKeySealingPCRs) if err != nil { - t.Errorf("Unseal operation failed with err: %v", err) - return + t.Fatalf("Unseal operation failed with err: %v", err) } if !reflect.DeepEqual(dataToSeal, unsealedData) { - t.Errorf("Seal/Unseal operation failed, want %v, but got %v", dataToSeal, unsealedData) + t.Fatalf("Seal/Unseal operation failed, want %v, but got %v", dataToSeal, unsealedData) } } @@ -51,35 +51,30 @@ func TestSealUnsealMismatchReport(t *testing.T) { rw, err := tpm2.OpenTPM(TpmDevicePath) if err != nil { - t.Errorf("OpenTPM failed with err: %v", err) - return + t.Fatalf("OpenTPM failed with err: %v", err) } defer rw.Close() dataToSeal := []byte("secret") if err := SealDiskKey(log, dataToSeal, DiskKeySealingPCRs); err != nil { - t.Errorf("Seal operation failed with err: %v", err) - return + t.Fatalf("Seal operation failed with err: %v", err) } pcrIndexes := [3]int{1, 7, 8} pcrValue := bytes.Repeat([]byte{0xF}, sha256.Size) for _, pcr := range pcrIndexes { if err = tpm2.PCRExtend(rw, tpmutil.Handle(pcr), tpm2.AlgSHA256, pcrValue, ""); err != nil { - t.Errorf("Failed to extend PCR %d: %s", pcr, err) - return + t.Fatalf("Failed to extend PCR %d: %s", pcr, err) } } _, err = UnsealDiskKey(DiskKeySealingPCRs) if err == nil { - t.Errorf("Expected error from UnsealDiskKey, got nil") - return + t.Fatalf("Expected error from UnsealDiskKey, got nil") } if !strings.Contains(err.Error(), "[1 7 8]") { - t.Errorf("UnsealDiskKey expected to report mismatching PCR indexes, got : %v", err) - return + t.Fatalf("UnsealDiskKey expected to report mismatching PCR indexes, got : %v", err) } } @@ -91,70 +86,58 @@ func TestSealUnsealTpmEventLogCollect(t *testing.T) { rw, err := tpm2.OpenTPM(TpmDevicePath) if err != nil { - t.Errorf("OpenTPM failed with err: %v", err) - return + t.Fatalf("OpenTPM failed with err: %v", err) } defer rw.Close() - // this should write the save the first event log + // this should write tpm event log to measurementLogSealSuccess file dataToSeal := []byte("secret") if err := SealDiskKey(log, dataToSeal, DiskKeySealingPCRs); err != nil { - t.Errorf("Seal operation failed with err: %v", err) - return + t.Fatalf("Seal operation failed with err: %v", err) } - // this won't write to event log, but still triggers saving it on unseal. + // this should cause UnsealDiskKey to fail pcrValue := bytes.Repeat([]byte{0xF}, sha256.Size) if err = tpm2.PCRExtend(rw, tpmutil.Handle(1), tpm2.AlgSHA256, pcrValue, ""); err != nil { - t.Errorf("Failed to extend PCR[1]: %v", err) - return + t.Fatalf("Failed to extend PCR[1]: %v", err) } - // this should fail and result in saving the second tpm event log + // this should fail and result in saving creating measurementLogUnsealFail _, err = UnsealDiskKey(DiskKeySealingPCRs) if err == nil { - t.Errorf("Expected error from UnsealDiskKey, got nil") - return + t.Fatalf("Expected error from UnsealDiskKey, got nil") } - // just check for tpm0 - sealSuccess := getLogCopyPath(measurementLogSealSuccess, 0) - sealFail := getLogCopyPath(measurementLogUnsealFail, 0) - if !fileutils.FileExists(nil, sealSuccess) { - t.Errorf("TPM measurement log \"%s\" not found, Expected to be copied", sealSuccess) - return + if !fileutils.FileExists(nil, measurementLogSealSuccess) { + t.Fatalf("TPM measurement log \"%s\" not found, expected to exist", measurementLogSealSuccess) } - if !fileutils.FileExists(nil, sealFail) { - t.Errorf("TPM measurement log \"%s\" not found, Expected to be copied", sealFail) - return + if !fileutils.FileExists(nil, measurementLogUnsealFail) { + t.Fatalf("TPM measurement log \"%s\" not found, expected to exist", measurementLogUnsealFail) } - // this should trigger collecting previous tpm event logs + // this should trigger backing up previously saved tpm event logs if err := SealDiskKey(log, dataToSeal, DiskKeySealingPCRs); err != nil { - t.Errorf("Seal operation failed with err: %v", err) - return + t.Fatalf("Seal operation failed with err: %v", err) } - // current measurement log should exist - if !fileutils.FileExists(nil, sealSuccess) { - t.Errorf("TPM measurement log \"%s\" not found, Expected to be copied", sealSuccess) - return + // a new measurementLogSealSuccess file should exist + if !fileutils.FileExists(nil, measurementLogSealSuccess) { + t.Fatalf("TPM measurement log \"%s\" not found, Expected to be copied", measurementLogSealSuccess) } - // this shouldn't exist because SealDiskKey will do a clean up - if fileutils.FileExists(nil, sealFail) { - t.Errorf("TPM measurement log \"%s\" found, Expected to not exist", sealFail) - return + + // measurementLogUnsealFail file shouldn't exist because SealDiskKey + // will do a clean up. + if fileutils.FileExists(nil, measurementLogUnsealFail) { + t.Fatalf("TPM measurement log \"%s\" found, Expected to not exist", measurementLogUnsealFail) } // backed up measurement logs both should exist - prevSealSuccess := getLogBackupPath(sealSuccess) - prevSealFail := getLogBackupPath(sealFail) + prevSealSuccess := fmt.Sprintf("%s-backup", measurementLogSealSuccess) + prevSealFail := fmt.Sprintf("%s-backup", measurementLogUnsealFail) if !fileutils.FileExists(nil, prevSealSuccess) { - t.Errorf("TPM measurement log \"%s\" not found, Expected to be backed up", prevSealSuccess) - return + t.Fatalf("TPM measurement log \"%s\" not found, Expected to be backed up", prevSealSuccess) } if !fileutils.FileExists(nil, prevSealFail) { - t.Errorf("TPM measurement log \"%s\" not found, Expected to be backed up", prevSealFail) - return + t.Fatalf("TPM measurement log \"%s\" not found, Expected to be backed up", prevSealFail) } } From 6348d33210f4f1758b25ca944acd2bdba67ca43b Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Wed, 22 Nov 2023 09:25:25 +0000 Subject: [PATCH 4/4] create TPM event log based on measure-config measurements Create a TCG formatted event log based on the measure-config measurements, we append this file to the end of the system TPM event log when eve fails to unlock the vault, this way we have more information about what possibly went wrong. Signed-off-by: Shahriyar Jalayeri --- pkg/measure-config/src/measurefs.go | 179 ++++++++++++++++++++++++---- pkg/pillar/evetpm/tpm.go | 69 ++++++----- 2 files changed, 196 insertions(+), 52 deletions(-) diff --git a/pkg/measure-config/src/measurefs.go b/pkg/measure-config/src/measurefs.go index 0bc7248d65..92e51dbd53 100644 --- a/pkg/measure-config/src/measurefs.go +++ b/pkg/measure-config/src/measurefs.go @@ -7,6 +7,7 @@ package main import ( "crypto/sha256" + "encoding/binary" "encoding/hex" "fmt" "io" @@ -25,24 +26,114 @@ const ( configPCRIndex = 14 configPCRHandle = tpmutil.Handle(tpm2.PCRFirst + configPCRIndex) //PCREvent (TPM2_PCR_Event) supports event size of maximum 1024 bytes. - maxEventDataSize = 1024 + maxEventDataSize = 1024 + evAlgSHA256 = 0xb + evEfiAction = 0x80000007 + measurefsTpmEventLog = "/persist/status/measurefs_tpm_event_log" ) +// the following structs created based on "Crypto Agile Log Entry Format" +// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf +type tcgPcrEvent2 struct { + PcrIndex uint32 + EventType uint32 + Digest tpmlDigestValues + EventSize uint32 + Event []uint8 +} + +type tpmlDigestValues struct { + Count uint32 + Digests []tpmtHa +} + +type tpmtHa struct { + HashAlg uint16 + DigestData []byte +} + type fileInfo struct { exist bool measureContent bool } type tpmEvent struct { - data string - pcr []byte + tcgEvent tcgPcrEvent2 + data string + pcr []byte +} + +func (event *tcgPcrEvent2) MarshalBinary() ([]byte, error) { + buffer := make([]byte, event.size()) + offset := 0 + + binary.LittleEndian.PutUint32(buffer[offset:], event.PcrIndex) + offset += 4 + + binary.LittleEndian.PutUint32(buffer[offset:], event.EventType) + offset += 4 + + digestBytes, err := event.Digest.MarshalBinary() + if err != nil { + return nil, err + } + + copy(buffer[offset:], digestBytes) + offset += len(digestBytes) + + binary.LittleEndian.PutUint32(buffer[offset:], event.EventSize) + offset += 4 + + copy(buffer[offset:], event.Event) + + return buffer, nil +} + +func (event *tcgPcrEvent2) size() int { + return 4 + 4 + event.Digest.size() + 4 + len(event.Event) +} + +func (digestValue *tpmlDigestValues) MarshalBinary() ([]byte, error) { + buffer := make([]byte, digestValue.size()) + offset := 0 + + binary.LittleEndian.PutUint32(buffer[offset:], digestValue.Count) + offset += 4 + + for _, digest := range digestValue.Digests { + digestBytes, err := digest.MarshalBinary() + if err != nil { + return nil, err + } + copy(buffer[offset:], digestBytes) + offset += len(digestBytes) + } + + return buffer, nil } -func min(a, b int) int { - if a < b { - return a +func (digestValue *tpmlDigestValues) size() int { + size := 4 + for _, digest := range digestValue.Digests { + size += digest.size() } - return b + return size +} + +func (digest *tpmtHa) MarshalBinary() ([]byte, error) { + buffer := make([]byte, digest.size()) + offset := 0 + + binary.LittleEndian.PutUint16(buffer[offset:], digest.HashAlg) + offset += 2 + + copy(buffer[offset:], digest.DigestData) + + return buffer, nil +} + +func (digest *tpmtHa) size() int { + return 2 + len(digest.DigestData) } // we do not measure content of following files @@ -107,7 +198,7 @@ func performMeasurement(filePath string, tpm io.ReadWriter, exist bool, content if content { hash, err := sha256sumForFile(filePath) if err != nil { - return nil, fmt.Errorf("can not measure %s :%v", filePath, err) + return nil, fmt.Errorf("can not measure %s :%w", filePath, err) } eventData = fmt.Sprintf("file:%s exist:true content-hash:%s", eventFilePath, hash) } else { @@ -118,15 +209,32 @@ func performMeasurement(filePath string, tpm io.ReadWriter, exist bool, content // associated with the PCR banks, and extends them all before return. err := tpm2.PCREvent(tpm, configPCRHandle, []byte(eventData)) if err != nil { - return nil, fmt.Errorf("can not measure %s. couldn't extend PCR: %v", filePath, err) + return nil, fmt.Errorf("can not measure %s. couldn't extend PCR: %w", filePath, err) } pcr, err := readConfigPCR(tpm) if err != nil { - return nil, fmt.Errorf("can not measure %s. couldn't read PCR: %v", filePath, err) + return nil, fmt.Errorf("can not measure %s. couldn't read PCR: %w", filePath, err) + } + + eventHash := sha256.Sum256([]byte(eventData)) + tcgEvent := tcgPcrEvent2{ + PcrIndex: configPCRIndex, + EventType: evEfiAction, + Digest: tpmlDigestValues{ + Count: 1, + Digests: []tpmtHa{ + { + HashAlg: evAlgSHA256, + DigestData: eventHash[:], + }, + }, + }, + EventSize: uint32(len(eventData)), + Event: []uint8(eventData), } - return &tpmEvent{eventData, pcr}, nil + return &tpmEvent{tcgEvent, eventData, pcr}, nil } func getFileMap() (map[string]fileInfo, error) { @@ -177,11 +285,12 @@ func getSortedFileList(files map[string]fileInfo) []string { return keys } -func measureConfig(tpm io.ReadWriter) error { +func measureConfig(tpm io.ReadWriter) ([]tcgPcrEvent2, error) { files, err := getFileMap() + events := make([]tcgPcrEvent2, 0) if err != nil { - return fmt.Errorf("can not get file list: %v", err) + return []tcgPcrEvent2{}, fmt.Errorf("can not get file list: %w", err) } //get sorted list of files. We must always go the same order @@ -203,23 +312,22 @@ func measureConfig(tpm io.ReadWriter) error { event, err = performMeasurement(file, tpm, false, false) } if err != nil { - return fmt.Errorf("can not measure %s: %v", file, err) + return []tcgPcrEvent2{}, fmt.Errorf("can not measure %s: %w", file, err) } + //Now we have a new value of PCR and an event - //TODO: add events to the event log, if event data exceeds 1024 bytes, - // make sure to break it into 1024 bytes chunks with added indicators - // (e.g. part n of m) to be able to reconstruct the even data for validation. - // for now we just print our measurements to boot log. log.Printf("%s pcr:%s", event.data, hex.EncodeToString(event.pcr)) + events = append(events, event.tcgEvent) } - return nil + + return events, nil } func readConfigPCR(tpm io.ReadWriter) ([]byte, error) { pcr, err := tpm2.ReadPCR(tpm, configPCRIndex, tpm2.AlgSHA256) if err != nil { - return nil, fmt.Errorf("can not read PCR %d: %v", configPCRIndex, err) + return nil, fmt.Errorf("can not read PCR %d: %w", configPCRIndex, err) } return pcr, nil } @@ -230,14 +338,37 @@ func readConfigPCR(tpm io.ReadWriter) ([]byte, error) { func main() { tpm, err := tpm2.OpenTPM(TpmDevicePath) if err != nil { - log.Printf("couldn't open TPM device %s. Exiting", TpmDevicePath) - return + log.Fatalf("couldn't open TPM device %s. Exiting", TpmDevicePath) } defer tpm.Close() - err = measureConfig(tpm) - + events, err := measureConfig(tpm) if err != nil { log.Fatal(err) } + + // loop over events and marshal them to binary + eventLog := make([]byte, 0) + for _, event := range events { + eventBytes, err := event.MarshalBinary() + if err != nil { + log.Printf("[WARNING] failed to construct measure-config tpm event log : %v", err) + return + } + + eventLog = append(eventLog, eventBytes...) + } + + // no need for an atomic file operations here, this file is created + // on every boot. + file, err := os.Create(measurefsTpmEventLog) + if err != nil { + log.Printf("[WARNING] failed to create measure-config tpm event log : %v", err) + return + } + defer file.Close() + + if _, err := file.Write(eventLog); err != nil { + log.Printf("[WARNING] failed to write measure-config tpm event log : %v", err) + } } diff --git a/pkg/pillar/evetpm/tpm.go b/pkg/pillar/evetpm/tpm.go index abb8c34a15..9272c8f72c 100644 --- a/pkg/pillar/evetpm/tpm.go +++ b/pkg/pillar/evetpm/tpm.go @@ -86,6 +86,9 @@ const ( // fails to unseal the vault key from TPM. measurementLogUnsealFail = types.PersistStatusDir + "/tpm_measurement_unseal_fail" + // measurefsTpmEventLog is the file containing the event log from the measure-config + measurefsTpmEventLog = types.PersistStatusDir + "/measurefs_tpm_event_log" + // measurementLogFile is a kernel exposed variable that contains the // TPM measurements and events log. measurementLogFile = "/hostfs/sys/kernel/security/tpm0/binary_bios_measurements" @@ -448,13 +451,13 @@ func writeDiskKey(key []byte) error { tpm2.AttrOwnerWrite|tpm2.AttrOwnerRead, uint16(len(key)), ); err != nil { - return fmt.Errorf("NVDefineSpace failed: %v", err) + return fmt.Errorf("NVDefineSpace failed: %w", err) } // Write the data if err := tpm2.NVWrite(rw, tpm2.HandleOwner, TpmDiskKeyHdl, EmptyPassword, key, 0); err != nil { - return fmt.Errorf("NVWrite failed: %v", err) + return fmt.Errorf("NVWrite failed: %w", err) } return nil } @@ -470,7 +473,7 @@ func readDiskKey() ([]byte, error) { keyBytes, err := tpm2.NVReadEx(rw, TpmDiskKeyHdl, tpm2.HandleOwner, EmptyPassword, 0) if err != nil { - return nil, fmt.Errorf("NVReadEx failed: %v", err) + return nil, fmt.Errorf("NVReadEx failed: %w", err) } return keyBytes, nil } @@ -590,12 +593,12 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro session, policy, err := PolicyPCRSession(rw, pcrSel) if err != nil { - return fmt.Errorf("PolicyPCRSession failed: %v", err) + return fmt.Errorf("PolicyPCRSession failed: %w", err) } //Don't need the handle, we need only the policy for sealing if err := tpm2.FlushContext(rw, session); err != nil { - return fmt.Errorf("flushing session handle %v failed: %v", session, err) + return fmt.Errorf("flushing session handle %v failed: %w", session, err) } priv, public, err := tpm2.Seal(rw, TpmSRKHdl, EmptyPassword, EmptyPassword, policy, key) @@ -613,13 +616,13 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro tpm2.AttrOwnerWrite|tpm2.AttrOwnerRead, uint16(len(priv)), ); err != nil { - return fmt.Errorf("NVDefineSpace %v failed: %v", TpmSealedDiskPrivHdl, err) + return fmt.Errorf("NVDefineSpace %v failed: %w", TpmSealedDiskPrivHdl, err) } // Write the private data if err := tpm2.NVWrite(rw, tpm2.HandleOwner, TpmSealedDiskPrivHdl, EmptyPassword, priv, 0); err != nil { - return fmt.Errorf("NVWrite %v failed: %v", TpmSealedDiskPrivHdl, err) + return fmt.Errorf("NVWrite %v failed: %w", TpmSealedDiskPrivHdl, err) } // Define space in NV storage @@ -632,12 +635,12 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro tpm2.AttrOwnerWrite|tpm2.AttrOwnerRead, uint16(len(public)), ); err != nil { - return fmt.Errorf("NVDefineSpace %v failed: %v", TpmSealedDiskPubHdl, err) + return fmt.Errorf("NVDefineSpace %v failed: %w", TpmSealedDiskPubHdl, err) } // Write the public data if err := tpm2.NVWrite(rw, tpm2.HandleOwner, TpmSealedDiskPubHdl, EmptyPassword, public, 0); err != nil { - return fmt.Errorf("NVWrite %v failed: %v", TpmSealedDiskPubHdl, err) + return fmt.Errorf("NVWrite %v failed: %w", TpmSealedDiskPubHdl, err) } // save a snapshot of current PCR values @@ -656,9 +659,7 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro } // fresh start, remove old copies of measurement logs. - if err := removeCopiedMeasurementLogs(); err != nil { - log.Warnf("removing old copies of TPM measurement log failed: %s", err) - } + removeCopiedMeasurementLogs() // save a copy of the current measurement log, this is also called // if unseal fails to have copy when we fail to unlock the vault. @@ -698,13 +699,13 @@ func UnsealDiskKey(pcrSel tpm2.PCRSelection) ([]byte, error) { priv, err := tpm2.NVReadEx(rw, TpmSealedDiskPrivHdl, tpm2.HandleOwner, EmptyPassword, 0) if err != nil { - return nil, fmt.Errorf("NVReadEx %v failed: %v", TpmSealedDiskPrivHdl, err) + return nil, fmt.Errorf("NVReadEx %v failed: %w", TpmSealedDiskPrivHdl, err) } // Read all of the data with NVReadEx pub, err := tpm2.NVReadEx(rw, TpmSealedDiskPubHdl, tpm2.HandleOwner, EmptyPassword, 0) if err != nil { - return nil, fmt.Errorf("NVReadEx %v failed: %v", TpmSealedDiskPubHdl, err) + return nil, fmt.Errorf("NVReadEx %v failed: %w", TpmSealedDiskPubHdl, err) } sealedObjHandle, _, err := tpm2.Load(rw, TpmSRKHdl, "", pub, priv) @@ -715,7 +716,7 @@ func UnsealDiskKey(pcrSel tpm2.PCRSelection) ([]byte, error) { session, _, err := PolicyPCRSession(rw, pcrSel) if err != nil { - return nil, fmt.Errorf("PolicyPCRSession failed: %v", err) + return nil, fmt.Errorf("PolicyPCRSession failed: %w", err) } defer tpm2.FlushContext(rw, session) @@ -753,7 +754,7 @@ func PolicyPCRSession(rw io.ReadWriteCloser, pcrSel tpm2.PCRSelection) (tpmutil. /*symmetric=*/ tpm2.AlgNull, /*authHash=*/ tpm2.AlgSHA256) if err != nil { - return tpm2.HandleNull, nil, fmt.Errorf("StartAuthSession failed: %v", err) + return tpm2.HandleNull, nil, fmt.Errorf("StartAuthSession failed: %w", err) } defer func() { if session != tpm2.HandleNull && err != nil { @@ -762,7 +763,7 @@ func PolicyPCRSession(rw io.ReadWriteCloser, pcrSel tpm2.PCRSelection) (tpmutil. }() if err = tpm2.PolicyPCR(rw, session, nil, pcrSel); err != nil { - return session, nil, fmt.Errorf("PolicyPCR failed: %v", err) + return session, nil, fmt.Errorf("PolicyPCR failed: %w", err) } policy, err := tpm2.PolicyGetDigest(rw, session) @@ -852,36 +853,48 @@ func backupCopiedMeasurementLogs() error { if fileutils.FileExists(nil, measurementLogSealSuccess) { if err := os.Rename(measurementLogSealSuccess, sealSuccessBackupPath); err != nil { - return fmt.Errorf("failed to backup tpm \"seal success event\" previously copied measurement log: %v", err) + return fmt.Errorf("failed to backup tpm \"seal success event\" previously copied measurement log: %w", err) } } if fileutils.FileExists(nil, measurementLogUnsealFail) { if err := os.Rename(measurementLogUnsealFail, unsealFailBackupPath); err != nil { _ = os.Rename(sealSuccessBackupPath, measurementLogSealSuccess) - return fmt.Errorf("failed to backup tpm \"unseal fail event\" previously copied measurement log: %v", err) + return fmt.Errorf("failed to backup tpm \"unseal fail event\" previously copied measurement log: %w", err) } } return nil } -func removeCopiedMeasurementLogs() error { - _ = os.Remove(measurementLogSealSuccess) - _ = os.Remove(measurementLogUnsealFail) - - return nil +func removeCopiedMeasurementLogs() { + os.Remove(measurementLogSealSuccess) + os.Remove(measurementLogUnsealFail) } func copyMeasurementLog(dstPath string) error { - measurementLogContent, err := os.ReadFile(measurementLogFile) + var appendErr error + tpmEventLog, err := os.ReadFile(measurementLogFile) if err != nil { - return fmt.Errorf("failed to read TPM measurements log file: %v", err) + return fmt.Errorf("failed to read TPM measurements log file: %w", err) + } + + measurefsEventLog, err := os.ReadFile(measurefsTpmEventLog) + if err == nil { + // append the measurefs event log to the tpm event log + tpmEventLog = append(tpmEventLog, measurefsEventLog...) + } else { + // don't fail yet, we might still be able to copy tpm event logs + appendErr = fmt.Errorf("failed to read measure-config measurements log file: %w", err) } - err = fileutils.WriteRename(dstPath, measurementLogContent) + err = fileutils.WriteRename(dstPath, tpmEventLog) if err != nil { - return fmt.Errorf("failed to copy stored measurement log file: %v", err) + if appendErr != nil { + return fmt.Errorf("failed to copy tpm and measurefs event logs: %w, %v", err, appendErr) + } + + return fmt.Errorf("failed to copy tpm measurement log data: %w", err) } return nil