[PATCH v2] tpmevlog: Ensure the event log matches the actual TPM PCRs
Jonathan McDowell
noodles at earth.li
Mon Sep 30 11:47:22 UTC 2024
Ultimately the TPM event log is used to be able to validate how the
current TPM PCRs values were arrived at. This is only useful if parsing
the event log results in the same values as the TPM actually has stored
in it. Add another minor test to the tpmevlog test that this is the
case.
Signed-off-by: Jonathan McDowell <noodles at meta.com>
---
v2:
- Move PCR extension helper into lib/src/fwts_tpm.c, fixing glib link
issue
src/lib/include/fwts_tpm.h | 5 ++
src/lib/src/fwts_tpm.c | 38 +++++++++++
src/tpm/tpmevlog/tpmevlog.c | 121 ++++++++++++++++++++++++++++++++++--
3 files changed, 159 insertions(+), 5 deletions(-)
diff --git a/src/lib/include/fwts_tpm.h b/src/lib/include/fwts_tpm.h
index 4816093d..41bc4c9e 100644
--- a/src/lib/include/fwts_tpm.h
+++ b/src/lib/include/fwts_tpm.h
@@ -32,6 +32,9 @@ PRAGMA_PACK_WARN_OFF
#define TPM2_SHA384_DIGEST_SIZE 48
#define TPM2_SHA512_DIGEST_SIZE 64
+/* Number of PCRs used for firmware, rather than OS, measurements */
+#define TPM2_FIRMWARE_PCR_COUNT 8
+
typedef uint16_t TPM2_ALG_ID;
#define TPM2_ALG_ERROR ((TPM2_ALG_ID) 0x0000)
#define TPM2_ALG_RSA ((TPM2_ALG_ID) 0x0001)
@@ -185,6 +188,8 @@ typedef struct {
void fwts_tpm_data_hexdump(fwts_framework *fw, const uint8_t *data,
const size_t size, const char *str);
+bool fwts_tpm_extend_pcr(uint8_t *pcr, const size_t pcr_len,
+ TPM2_ALG_ID alg, const uint8_t *data);
uint8_t fwts_tpm_get_hash_size(const TPM2_ALG_ID hash);
PRAGMA_POP
diff --git a/src/lib/src/fwts_tpm.c b/src/lib/src/fwts_tpm.c
index abdd7a41..1f3125dc 100644
--- a/src/lib/src/fwts_tpm.c
+++ b/src/lib/src/fwts_tpm.c
@@ -20,6 +20,8 @@
#include "fwts.h"
#include "fwts_tpm.h"
+#include <glib.h>
+
/*
* fwts_tpm_data_hexdump
* hex dump of a tpm event log data
@@ -42,6 +44,42 @@ void fwts_tpm_data_hexdump(
}
}
+/*
+ * fwts_tpm_extend_pcr
+ * extend a PCR hash given the supplied event data
+ */
+bool fwts_tpm_extend_pcr(uint8_t *pcr, const size_t pcr_len,
+ TPM2_ALG_ID alg, const uint8_t *data)
+{
+ GChecksum *hasher;
+ gsize hash_len;
+
+ hash_len = fwts_tpm_get_hash_size(alg);
+ if (hash_len > pcr_len)
+ return false;
+
+ switch (alg) {
+ case TPM2_ALG_SHA256:
+ hasher = g_checksum_new(G_CHECKSUM_SHA256);
+ break;
+ case TPM2_ALG_SHA384:
+ hasher = g_checksum_new(G_CHECKSUM_SHA384);
+ break;
+ case TPM2_ALG_SHA512:
+ hasher = g_checksum_new(G_CHECKSUM_SHA512);
+ break;
+ default:
+ return false;
+ }
+
+ g_checksum_update(hasher, pcr, hash_len);
+ g_checksum_update(hasher, data, hash_len);
+ g_checksum_get_digest(hasher, pcr, &hash_len);
+ g_checksum_free(hasher);
+
+ return true;
+}
+
/*
* fwts_tpm_evlog_type_to_string
* get hash size
diff --git a/src/tpm/tpmevlog/tpmevlog.c b/src/tpm/tpmevlog/tpmevlog.c
index d06638f0..70a79854 100644
--- a/src/tpm/tpmevlog/tpmevlog.c
+++ b/src/tpm/tpmevlog/tpmevlog.c
@@ -19,6 +19,7 @@
#include "fwts.h"
#include <sys/types.h>
+#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
@@ -27,8 +28,60 @@
#include "fwts_tpm.h"
#include "fwts_uefi.h"
+#define FWTS_TPM_DEVICE_PATH "/sys/class/tpm"
#define FWTS_TPM_LOG_DIR_PATH "/sys/kernel/security"
+struct tpm_pcr_state {
+ uint8_t pcr[TPM2_FIRMWARE_PCR_COUNT][TPM2_SHA256_DIGEST_SIZE];
+};
+
+static bool tpmevlog_parsed = false;
+static struct tpm_pcr_state tpmevlog_pcrs = {};
+
+static int tpmevlog_read_device_pcrs(fwts_framework *fw, const char *tpm, struct tpm_pcr_state *pcrs)
+{
+ char path[PATH_MAX];
+ char buffer[TPM2_SHA256_DIGEST_SIZE * 2 + 2]; /* Trailing \n + NUL */
+ int i, j;
+ int fd;
+
+ memset(pcrs, 0, sizeof(*pcrs));
+ for (i = 0; i < TPM2_FIRMWARE_PCR_COUNT; i++) {
+ snprintf(path, sizeof(path), FWTS_TPM_DEVICE_PATH "/%s/pcr-sha256/%d",
+ tpm, i);
+
+ if ((fd = open(path, O_RDONLY)) >= 0) {
+ const ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
+
+ if (n <= 0 || buffer[n - 1] != '\n') {
+ fwts_log_info(fw, "Cannot read TPM PCR%d value.", i);
+ (void)close(fd);
+ return FWTS_ERROR;
+ }
+
+ for (j = 0; j < (n - 1); j++) {
+ char c = toupper(buffer[j]);
+
+ if (!isxdigit(c)) {
+ fwts_log_info(fw, "Cannot parse TPM PCR%d value.", i);
+ (void)close(fd);
+ return FWTS_ERROR;
+ }
+
+ if (j & 1) {
+ pcrs->pcr[i][j >> 1] |= isdigit(c) ? c - '0' : c - 'A' + 10;
+ } else {
+ pcrs->pcr[i][j >> 1] = (isdigit(c) ? c - '0' : c - 'A' + 10) << 4;
+ }
+ }
+
+ (void)close(fd);
+ }
+ }
+
+ return FWTS_OK;
+}
+
static int tpmevlog_pcrindex_value_check(fwts_framework *fw, const uint32_t pcr)
{
/*
@@ -200,7 +253,8 @@ static int tpmevlog_v2_check(
fwts_pc_client_pcr_event *pc_event;
fwts_efi_spec_id_event *specid_evcent;
fwts_spec_id_event_alg_sz *alg_sz;
- bool separator_seen[8] = { false };
+ bool separator_seen[TPM2_FIRMWARE_PCR_COUNT] = { false };
+ bool startuplocality_seen = false;
/* specid_event_check */
if (len < sizeof(fwts_pc_client_pcr_event)) {
@@ -361,6 +415,18 @@ static int tpmevlog_v2_check(
return FWTS_ERROR;
}
+ /*
+ * Extend the current PCR hash with the new hash from the log
+ */
+ if ((!tpmevlog_parsed) && (alg_id == TPM2_ALG_SHA256) &&
+ (pcr_event2->event_type != EV_NO_ACTION) &&
+ (pcr_event2->pcr_index < TPM2_FIRMWARE_PCR_COUNT)) {
+ fwts_tpm_extend_pcr(tpmevlog_pcrs.pcr[pcr_event2->pcr_index],
+ TPM2_SHA256_DIGEST_SIZE,
+ alg_id,
+ pdata);
+ }
+
pdata += hash_size;
len_remain -= hash_size;
}
@@ -381,15 +447,33 @@ static int tpmevlog_v2_check(
if (ret != FWTS_OK)
return ret;
- if ((pcr_event2->pcr_index < 8) && (pcr_event2->event_type == EV_SEPARATOR))
+ if ((pcr_event2->pcr_index < TPM2_FIRMWARE_PCR_COUNT) &&
+ (pcr_event2->event_type == EV_SEPARATOR))
separator_seen[pcr_event2->pcr_index] = true;
- pdata += (event_size + sizeof(event_size));
- len_remain -= (event_size + sizeof(event_size));
+ pdata += sizeof(event_size);
+ len_remain -= sizeof(event_size);
+
+ if (pcr_event2->event_type == EV_NO_ACTION &&
+ pcr_event2->pcr_index == 0 &&
+ event_size == 17 &&
+ memcmp(pdata, "StartupLocality", sizeof("StartupLocality")) == 0) {
+ if (!startuplocality_seen) {
+ tpmevlog_pcrs.pcr[0][TPM2_SHA256_DIGEST_SIZE - 1] =
+ pdata[sizeof("StartupLocality")];
+ startuplocality_seen = true;
+ } else {
+ fwts_failed(fw, LOG_LEVEL_MEDIUM, "EventV2StartupLocalitySeenTwice",
+ "Event log contained more than one StartupLocality event");
+ return FWTS_ERROR;
+ }
+ }
+ pdata += event_size;
+ len_remain -= event_size;
}
- for (i = 0; i < 8; i++) {
+ for (i = 0; i < TPM2_FIRMWARE_PCR_COUNT; i++) {
if (!separator_seen[i]) {
fwts_failed(fw, LOG_LEVEL_MEDIUM, "EventV2SeparatorSeen",
"PCR %d did not have EV_SEPARATOR measured into it at "
@@ -398,6 +482,7 @@ static int tpmevlog_v2_check(
}
}
+ tpmevlog_parsed = true;
fwts_passed(fw, "Check TPM crypto agile event log test passed.");
return FWTS_OK;
}
@@ -551,8 +636,34 @@ static int tpmevlog_test1(fwts_framework *fw)
return FWTS_OK;
}
+static int tpmevlog_test2(fwts_framework *fw)
+{
+ struct tpm_pcr_state pcrs_actual = {};
+ if (!tpmevlog_parsed) {
+ fwts_skipped(fw, "No TPM 2.0 event log has not been parsed, skipping.");
+ return FWTS_SKIP;
+ }
+
+ if (tpmevlog_read_device_pcrs(fw, "tpm0", &pcrs_actual) != FWTS_OK) {
+ fwts_skipped(fw, "Could not read PCRs from TPM, skipping.");
+ return FWTS_SKIP;
+ }
+
+ for (int i = 0; i < TPM2_FIRMWARE_PCR_COUNT; i++) {
+ if (memcmp(pcrs_actual.pcr[i], tpmevlog_pcrs.pcr[i], TPM2_SHA256_DIGEST_SIZE) != 0) {
+ fwts_failed(fw, LOG_LEVEL_HIGH, "PCRsMatchEvLog",
+ "PCR %d differs between actual value and log.", i);
+ return FWTS_ERROR;
+ }
+ }
+
+ fwts_passed(fw, "Check TPM event log machines actual TPM PCRs.");
+ return FWTS_OK;
+}
+
static fwts_framework_minor_test tpmevlog_tests[] = {
{ tpmevlog_test1, "Sanity check TPM event log." },
+ { tpmevlog_test2, "Check TPM event log matches TPM PCRs." },
{ NULL, NULL }
};
--
2.39.5
More information about the fwts-devel
mailing list