Skip to content

Commit

Permalink
Save EFI BootX variables when (un)sealing storage key
Browse files Browse the repository at this point in the history
To do a proper analysis a monitor TUI we need values of BootXXXX and
BootOrder EFI variables. Variable data for them is present in the TPM
log however neither TCG spec nor UEFI spec mandate what part of
EFI_LOAD_OPTION structure is serialized and measured in the TPM log so
the reliable extraction is not possible. For example UEFI on NUC13
device avoids first two fields of this structure.

- save variables to /persist/status/boot_vars/{success,fail} directories
  Export paths by introducing GetBootVariablesDirNames()
- export paths to saved TPM logs by introducing GetTpmLogFileNames()
  and GetTpmLogBackupFileNames()

Signed-off-by: Mikhail Malyshev <[email protected]>
  • Loading branch information
rucoder committed Feb 13, 2025
1 parent 41289af commit 81db9bf
Showing 1 changed file with 89 additions and 2 deletions.
91 changes: 89 additions & 2 deletions pkg/pillar/evetpm/tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
"io"
"math/big"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"unsafe"

"github.com/google/go-tpm/legacy/tpm2"
Expand Down Expand Up @@ -82,6 +85,10 @@ const (
PCRBank256StatusNotSupported
)

// as defined in https://uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
// 3.3 Globally Defined Variables. must be LOWERCASE
const efiGlobalVariableGUID = "8be4df61-93ca-11d2-aa0d-00e098032b8c"

var (
//EcdhKeyFile is the location of the ecdh private key
//on devices without a TPM. It is not a constant due to test usage
Expand Down Expand Up @@ -120,6 +127,16 @@ var (
// it is not a constant due to test usage.
measurefsTpmEventLog = types.PersistStatusDir + "/measurefs_tpm_event_log"

// we do not make backup copies of following directories because we use them
// only when we couldn't unseal the key from TPM and remote attestation fails
// to get a backup key
// directory to store the boot variables's values when the key is sealed
bootVariablesSealSuccess = filepath.Join(types.PersistStatusDir, "boot_vars/success")
// directory to store the boot variables's values when we failed to unseal the key
bootVariablesUnsealFail = filepath.Join(types.PersistStatusDir, "boot_vars/fail")
// sysfs directory with boot variables
kernelEfiBootVarsPath = "/hostfs/sys/firmware/efi/efivars/"

// PcrSelection is used as an entropy to generate keys and the selection
// of PCRs do not matter as well as the contents but PCR[7] is not changed often
// on our devices
Expand Down Expand Up @@ -224,6 +241,23 @@ var (
}
)

// GetTpmLogFileNames returns paths to saved TPM logs
func GetTpmLogFileNames() (string, string) {
return measurementLogSealSuccess, measurementLogUnsealFail
}

// GetTpmLogBackupFileNames returns paths to saved TPM logs for previous boot
func GetTpmLogBackupFileNames() (string, string) {
sealSuccessBackupPath := fmt.Sprintf("%s-backup", measurementLogSealSuccess)
unsealFailBackupPath := fmt.Sprintf("%s-backup", measurementLogUnsealFail)
return sealSuccessBackupPath, unsealFailBackupPath
}

// GetBootVariablesDirNames returns paths to saved boot variables directories
func GetBootVariablesDirNames() (string, string) {
return bootVariablesSealSuccess, bootVariablesUnsealFail
}

// SealedKeyType holds different types of sealed key
// defined below
type SealedKeyType uint32
Expand Down Expand Up @@ -819,6 +853,12 @@ func SealDiskKey(log *base.LogObject, key []byte, pcrSel tpm2.PCRSelection) erro
log.Warnf("copying current TPM measurement log failed: %s", err)
}

// save a copy of the current boot variables, this is also called
// if unseal fails to have copy when we fail to unlock the vault.
if err := saveBootVariables(bootVariablesSealSuccess); err != nil {
log.Warnf("copying current boot variables failed: %s", err)
}

return nil
}

Expand Down Expand Up @@ -883,6 +923,13 @@ func UnsealDiskKey(pcrSel tpm2.PCRSelection) ([]byte, error) {
evtLogStat = fmt.Sprintf("copying (failed unseal) TPM measurement log failed: %v", errEvtLog)
}

// save a copy of the current boot variables
if errSaveVars := saveBootVariables(bootVariablesUnsealFail); errSaveVars != nil {
// just report the failure, still give FindMismatchingPCRs a chance so
// we can at least have some partial information about why unseal failed.
evtLogStat += fmt.Sprintf(" ,copying (failed unseal) boot variables failed: %v", errSaveVars)
}

// try to find out the mismatching PCR index
mismatch, errPcrMiss := FindMismatchingPCRs()
if errPcrMiss != nil {
Expand Down Expand Up @@ -1000,8 +1047,7 @@ func pcrBankSHA256EnabledHelper() bool {
}

func backupCopiedMeasurementLogs() error {
sealSuccessBackupPath := fmt.Sprintf("%s-backup", measurementLogSealSuccess)
unsealFailBackupPath := fmt.Sprintf("%s-backup", measurementLogUnsealFail)
sealSuccessBackupPath, unsealFailBackupPath := GetTpmLogBackupFileNames()

if fileutils.FileExists(nil, measurementLogSealSuccess) {
if err := os.Rename(measurementLogSealSuccess, sealSuccessBackupPath); err != nil {
Expand Down Expand Up @@ -1052,6 +1098,47 @@ func copyMeasurementLog(dstPath string) error {
return nil
}

func saveBootVariables(destPath string) error {
if err := os.RemoveAll(destPath); err != nil {
return fmt.Errorf("failed to clean directory %s: %w", destPath, err)
}

if err := os.MkdirAll(destPath, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", destPath, err)
}

files, err := os.ReadDir(kernelEfiBootVarsPath)
if err != nil {
return fmt.Errorf("failed to read directory %s: %w", kernelEfiBootVarsPath, err)
}

variableSuffix := fmt.Sprintf("-%s", efiGlobalVariableGUID)
bootOrderFileName := fmt.Sprintf(`BootOrder%s`, variableSuffix)
regexpStr := fmt.Sprintf(`^Boot[0-9a-fA-F]{4}%s$`, variableSuffix)

// regexp to match BootXXXX where XXXX is a 4 digit hex number
bootVarRegexp := regexp.MustCompile(regexpStr)

for _, file := range files {
variableFileName := file.Name()

if variableFileName == bootOrderFileName || bootVarRegexp.MatchString(variableFileName) {
src := filepath.Join(kernelEfiBootVarsPath, variableFileName)

//remove suffix for destination file
dst := filepath.Join(destPath, variableFileName)
dst = strings.TrimSuffix(dst, variableSuffix)

// copy the file
if err := fileutils.CopyFile(src, dst); err != nil {
return fmt.Errorf("failed to copy file %s to %s: %w", src, dst, err)
}
}
}

return nil
}

func saveDiskKeySealingPCRs() error {
trw, err := tpm2.OpenTPM(TpmDevicePath)
if err != nil {
Expand Down

0 comments on commit 81db9bf

Please sign in to comment.