Skip to content

Commit a0413e3

Browse files
shjalarouming
authored andcommitted
tpm : save tpm event logs
When unseal operation fails due to a policy failure, eve tries to find out which PCR mismatched, while useful, this information can't help diagnosing the issue in details. By saving tpm event log when sealing the key and when unseal operation fails, we can further analyze the problem by parsing and diffing the event logs. Signed-off-by: Shahriyar Jalayeri <[email protected]>
1 parent b38a8bf commit a0413e3

File tree

2 files changed

+254
-7
lines changed

2 files changed

+254
-7
lines changed

pkg/pillar/cmd/tpmmgr/tpmmgr_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/google/go-tpm/tpm2"
2222
"github.com/google/go-tpm/tpmutil"
2323
etpm "github.com/lf-edge/eve/pkg/pillar/evetpm"
24+
fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file"
2425
)
2526

2627
const ecdhCertPem = `
@@ -268,3 +269,79 @@ func TestSealUnsealMismatchReport(t *testing.T) {
268269
return
269270
}
270271
}
272+
273+
func TestSealUnsealTpmEventLogCollect(t *testing.T) {
274+
_, err := os.Stat(etpm.TpmDevicePath)
275+
if err != nil {
276+
t.Skip("TPM is not available, skipping the test.")
277+
}
278+
279+
rw, err := tpm2.OpenTPM(etpm.TpmDevicePath)
280+
if err != nil {
281+
t.Errorf("OpenTPM failed with err: %v", err)
282+
return
283+
}
284+
defer rw.Close()
285+
286+
// this should write the save the first event log
287+
dataToSeal := []byte("secret")
288+
if err := etpm.SealDiskKey(dataToSeal, etpm.DiskKeySealingPCRs); err != nil {
289+
t.Errorf("Seal operation failed with err: %v", err)
290+
return
291+
}
292+
293+
// this won't write to event log, but still triggers saving it on unseal.
294+
pcrValue := bytes.Repeat([]byte{0xF}, sha256.Size)
295+
if err = tpm2.PCRExtend(rw, tpmutil.Handle(1), tpm2.AlgSHA256, pcrValue, ""); err != nil {
296+
t.Errorf("Failed to extend PCR[1]: %v", err)
297+
return
298+
}
299+
300+
// this should fail and result in saving the second tpm event log
301+
_, err = etpm.UnsealDiskKey(etpm.DiskKeySealingPCRs)
302+
if err == nil {
303+
t.Errorf("Expected error from UnsealDiskKey, got nil")
304+
return
305+
}
306+
307+
// just check for tpm0
308+
sealSuccess := fmt.Sprintf(etpm.TpmEvtLogSavePattern, etpm.MeasurementLogSealSuccess, 0)
309+
sealFail := fmt.Sprintf(etpm.TpmEvtLogSavePattern, etpm.MeasurementLogUnsealFail, 0)
310+
if !fileutils.FileExists(nil, sealSuccess) {
311+
t.Errorf("TPM event log \"%s\" not found, Expected to be saved", sealSuccess)
312+
return
313+
}
314+
if !fileutils.FileExists(nil, sealFail) {
315+
t.Errorf("TPM event log \"%s\" not found, Expected to be saved", sealFail)
316+
return
317+
}
318+
319+
// this should trigger collecting previous tpm event logs
320+
if err := etpm.SealDiskKey(dataToSeal, etpm.DiskKeySealingPCRs); err != nil {
321+
t.Errorf("Seal operation failed with err: %v", err)
322+
return
323+
}
324+
325+
// current event log should exist
326+
if !fileutils.FileExists(nil, sealSuccess) {
327+
t.Errorf("TPM event log \"%s\" not found, Expected to be saved", sealSuccess)
328+
return
329+
}
330+
// this shouldn't exist because SealDiskKey will do a clean up
331+
if fileutils.FileExists(nil, sealFail) {
332+
t.Errorf("TPM event log \"%s\" found, Expected to not exist", sealFail)
333+
return
334+
}
335+
336+
// collected event logs both should exist
337+
prevSealSuccess := fmt.Sprintf(etpm.TpmEvtLogCollectPattern, sealSuccess)
338+
prevSealFail := fmt.Sprintf(etpm.TpmEvtLogCollectPattern, sealFail)
339+
if !fileutils.FileExists(nil, prevSealSuccess) {
340+
t.Errorf("TPM event log \"%s\" not found, Expected to be collected", prevSealSuccess)
341+
return
342+
}
343+
if !fileutils.FileExists(nil, prevSealFail) {
344+
t.Errorf("TPM event log \"%s\" not found, Expected to be collected", prevSealFail)
345+
return
346+
}
347+
}

pkg/pillar/evetpm/tpm.go

Lines changed: 177 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"io/ioutil"
1717
"math/big"
1818
"os"
19+
"path/filepath"
1920
"sort"
2021
"unsafe"
2122

@@ -77,6 +78,29 @@ const (
7778
// TpmSavedDiskSealingPcrs is the file that holds a copy of PCR values
7879
// at the time of generating and sealing the disk key into the TPM.
7980
TpmSavedDiskSealingPcrs = types.PersistStatusDir + "/sealingpcrs"
81+
82+
// MeasurementLogSealSuccess is files that holds a copy of event log at the time
83+
// of generating/sealing the disk key into the TPM.
84+
MeasurementLogSealSuccess = types.PersistStatusDir + "/tpm_measurement_seal_success"
85+
86+
// MeasurementLogUnsealFail is files that holds a copy of event log at the time EVE
87+
// fails to unseal the vault key from TPM.
88+
MeasurementLogUnsealFail = types.PersistStatusDir + "/tpm_measurement_unseal_fail"
89+
90+
// TpmEvtLogSavePattern contains the pattern used to save event log files,
91+
// considering the case when multiple tpm devices are present.
92+
TpmEvtLogSavePattern = "%s-tpm%d"
93+
94+
// TpmEvtLogCollectPattern contains the pattern used to collect event log files.
95+
TpmEvtLogCollectPattern = "%s-backup"
96+
97+
// measurementLogFile is a kernel exposed variable that contains the
98+
// TPM measurements and events log.
99+
measurementLogFile = "binary_bios_measurements"
100+
101+
// syfsTpmDir is directory that TPMs get mapped on sysfs, and it contains
102+
// measurement logs.
103+
syfsTpmDir = "/hostfs/sys/kernel/security/tpm*"
80104
)
81105

82106
// PCRBank256Status stores info about support for
@@ -628,11 +652,31 @@ func SealDiskKey(key []byte, pcrSel tpm2.PCRSelection) error {
628652
return fmt.Errorf("NVWrite %v failed: %v", TpmSealedDiskPubHdl, err)
629653
}
630654

631-
// save a snapshot of PCR values
655+
// save a snapshot of current PCR values
632656
if err := saveDiskKeySealingPCRs(TpmSavedDiskSealingPcrs); err != nil {
633657
return fmt.Errorf("saving snapshot of sealing PCRs failed: %w", err)
634658
}
635659

660+
// Backup the previous pair of logs if any, so at most we have two pairs of
661+
// measurement logs (per available tpm devices). This is needed because if the
662+
// failing devices get connected to the controller and collects the backup key,
663+
// we end up here again and will override the MeasurementLogSealSuccess with
664+
// current measurement log (which is same as the content of MeasurementLogSealFail)
665+
// and lose the ability to diff and diagnose the issue.
666+
if err := backupCopiedMeasurementLogs(); err != nil {
667+
return fmt.Errorf("collecting previous snapshot of TPM event log failed: %w", err)
668+
}
669+
670+
// fresh start, remove old copies of measurement logs.
671+
if err := removeCopiedMeasurementLogs(); err != nil {
672+
return fmt.Errorf("removing old copies of TPM measurement log failed: %w", err)
673+
}
674+
675+
// save a copy of the current measurement log
676+
if err := copyMeasurementLog(MeasurementLogSealSuccess); err != nil {
677+
return fmt.Errorf("copying current TPM measurement log failed: %w", err)
678+
}
679+
636680
return nil
637681
}
638682

@@ -688,14 +732,22 @@ func UnsealDiskKey(pcrSel tpm2.PCRSelection) ([]byte, error) {
688732

689733
key, err := tpm2.UnsealWithSession(rw, session, sealedObjHandle, EmptyPassword)
690734
if err != nil {
691-
// We get here mostly because of RCPolicyFail error, so try to get more
692-
// information about the failure by finding the mismatching PCR index.
693-
mismatch, extraErr := findMismatchingPCRs(TpmSavedDiskSealingPcrs)
694-
if extraErr != nil {
695-
return nil, fmt.Errorf("UnsealWithSession failed: %w, getting more info failed: %v", err, extraErr)
735+
// We get here mostly because of RCPolicyFail error, so first try to save
736+
// a copy of TPM measurement log, it comes handy for diagnosing the issue.
737+
evtLogStat := "copied (failed unseal) TPM measurement log"
738+
if errEvtLog := copyMeasurementLog(MeasurementLogUnsealFail); errEvtLog != nil {
739+
// just report the failure, still give findMismatchingPCRs a chance so
740+
// we can at least have some partial information about why unseal failed.
741+
evtLogStat = fmt.Sprintf("copying (failed unseal) TPM measurement log failed: %v", errEvtLog)
742+
}
743+
744+
// try to find out the mismatching PCR index
745+
mismatch, errPcrMiss := findMismatchingPCRs(TpmSavedDiskSealingPcrs)
746+
if errPcrMiss != nil {
747+
return nil, fmt.Errorf("UnsealWithSession failed: %w, %s, finding mismatching PCR failed: %v", err, evtLogStat, errPcrMiss)
696748
}
697749

698-
return nil, fmt.Errorf("UnsealWithSession failed: %w, possibly mismatching PCR indexes %v", err, mismatch)
750+
return nil, fmt.Errorf("UnsealWithSession failed: %w, %s, possibly mismatching PCR indexes: %v", err, evtLogStat, mismatch)
699751
}
700752
return key, nil
701753
}
@@ -805,6 +857,124 @@ func pcrBankSHA256EnabledHelper() bool {
805857
return err == nil
806858
}
807859

860+
func getMappedTpmsPath() ([]string, error) {
861+
paths, err := filepath.Glob(syfsTpmDir)
862+
if err != nil {
863+
return nil, fmt.Errorf("failed to enumerate TPM(s) in sysfs: %w", err)
864+
} else if len(paths) == 0 {
865+
return nil, fmt.Errorf("found no TPM in sysfs")
866+
}
867+
868+
return paths, nil
869+
}
870+
871+
func countMappedTpms() (int, error) {
872+
paths, err := getMappedTpmsPath()
873+
if err != nil {
874+
return 0, fmt.Errorf("getMappedTpmsPath failed: %w", err)
875+
}
876+
877+
return len(paths), nil
878+
}
879+
880+
func getMeasurementLogFiles() ([]string, error) {
881+
paths, err := getMappedTpmsPath()
882+
if err != nil {
883+
return nil, fmt.Errorf("getMappedTpmsPath failed: %w", err)
884+
}
885+
886+
enumerated := make([]string, 0)
887+
for _, path := range paths {
888+
fullPath := filepath.Join(path, measurementLogFile)
889+
if fileutils.FileExists(nil, fullPath) {
890+
enumerated = append(enumerated, fullPath)
891+
}
892+
}
893+
894+
return enumerated, nil
895+
}
896+
897+
func backupCopiedMeasurementLogs() error {
898+
counted, err := countMappedTpms()
899+
if err != nil {
900+
return fmt.Errorf("countMappedTPMs failed: %w", err)
901+
}
902+
903+
leftToBackup := counted
904+
for i := 0; i < counted; i++ {
905+
sealSuccessPath := fmt.Sprintf(TpmEvtLogSavePattern, MeasurementLogSealSuccess, i)
906+
unsealFailPath := fmt.Sprintf(TpmEvtLogSavePattern, MeasurementLogUnsealFail, i)
907+
if fileutils.FileExists(nil, sealSuccessPath) && fileutils.FileExists(nil, unsealFailPath) {
908+
sealSuccessBackupPath := fmt.Sprintf(TpmEvtLogCollectPattern, sealSuccessPath)
909+
unsealFailBackupPath := fmt.Sprintf(TpmEvtLogCollectPattern, unsealFailPath)
910+
if err := os.Rename(sealSuccessPath, sealSuccessBackupPath); err != nil {
911+
fmt.Fprintf(os.Stderr, "failed to backup tpm%d \"seal success\" previously copied measurement log: %v", i, err)
912+
continue
913+
}
914+
if err := os.Rename(unsealFailPath, unsealFailBackupPath); err != nil {
915+
fmt.Fprintf(os.Stderr, "failed to backup tpm%d \"unseal fail\" previously copied measurement log: %v", i, err)
916+
_ = os.Rename(sealSuccessBackupPath, sealSuccessPath)
917+
continue
918+
}
919+
}
920+
921+
leftToBackup--
922+
}
923+
924+
if leftToBackup != 0 {
925+
return fmt.Errorf("failed to backup %d number of previously copied TPM measurement logs", leftToBackup)
926+
}
927+
928+
return nil
929+
}
930+
931+
func removeCopiedMeasurementLogs() error {
932+
counted, err := countMappedTpms()
933+
if err != nil {
934+
return fmt.Errorf("countMappedTPMs failed: %w", err)
935+
}
936+
937+
for i := 0; i < counted; i++ {
938+
sealSuccessPath := fmt.Sprintf(TpmEvtLogSavePattern, MeasurementLogSealSuccess, i)
939+
unsealFailPath := fmt.Sprintf(TpmEvtLogSavePattern, MeasurementLogUnsealFail, i)
940+
_ = os.Remove(sealSuccessPath)
941+
_ = os.Remove(unsealFailPath)
942+
}
943+
944+
return nil
945+
}
946+
947+
func copyMeasurementLog(dstPath string) error {
948+
paths, err := getMeasurementLogFiles()
949+
if err != nil {
950+
return fmt.Errorf("enumSourceEventLogFiles failed: %w", err)
951+
}
952+
953+
leftToCopy := len(paths)
954+
for i, path := range paths {
955+
measurementLogContent, err := os.ReadFile(path)
956+
if err != nil {
957+
fmt.Fprintf(os.Stderr, "failed to read stored measurement log file: %v", err)
958+
continue
959+
}
960+
961+
copyPath := fmt.Sprintf(TpmEvtLogSavePattern, dstPath, i)
962+
err = fileutils.WriteRename(copyPath, measurementLogContent)
963+
if err != nil {
964+
fmt.Fprintf(os.Stderr, "failed to copy stored measurement log file: %v", err)
965+
continue
966+
}
967+
968+
leftToCopy--
969+
}
970+
971+
if leftToCopy != 0 {
972+
return fmt.Errorf("failed to copy %d number of stored measurement log files", leftToCopy)
973+
}
974+
975+
return nil
976+
}
977+
808978
func saveDiskKeySealingPCRs(pcrsFile string) error {
809979
trw, err := tpm2.OpenTPM(TpmDevicePath)
810980
if err != nil {

0 commit comments

Comments
 (0)