| 1 | #if defined (UEFI) |
| 2 | |
| 3 | #include <stdint.h> |
| 4 | #include <stddef.h> |
| 5 | #include <stdbool.h> |
| 6 | #include <efi.h> |
| 7 | #include <efi/protocol/efitcg2.h> |
| 8 | #include <efi/protocol/eficc.h> |
| 9 | #include <lib/tpm.h> |
| 10 | #include <lib/misc.h> |
| 11 | #include <lib/print.h> |
| 12 | #include <lib/libc.h> |
| 13 | #include <mm/pmm.h> |
| 14 | |
| 15 | // TCG event log entry layouts (TCG PC Client Platform Firmware Profile). |
| 16 | struct tpm_pcr_event_v1_2 { |
| 17 | uint32_t pcr_idx; |
| 18 | uint32_t event_type; |
| 19 | uint8_t digest[20]; |
| 20 | uint32_t event_size; |
| 21 | uint8_t event[]; |
| 22 | } __attribute__((packed)); |
| 23 | |
| 24 | struct tpm_specid_event_alg { |
| 25 | uint16_t alg_id; |
| 26 | uint16_t digest_size; |
| 27 | } __attribute__((packed)); |
| 28 | |
| 29 | struct tpm_specid_event_head { |
| 30 | uint8_t signature[16]; |
| 31 | uint32_t platform_class; |
| 32 | uint8_t spec_version_minor; |
| 33 | uint8_t spec_version_major; |
| 34 | uint8_t spec_errata; |
| 35 | uint8_t uintn_size; |
| 36 | uint32_t num_algs; |
| 37 | struct tpm_specid_event_alg digest_sizes[]; |
| 38 | } __attribute__((packed)); |
| 39 | |
| 40 | // Followed by `count` digests (uint16_t alg_id + variable-length digest), |
| 41 | // then a uint32_t event_size and event_size bytes of event data. |
| 42 | struct tpm_pcr_event2_head { |
| 43 | uint32_t pcr_idx; |
| 44 | uint32_t event_type; |
| 45 | uint32_t count; |
| 46 | } __attribute__((packed)); |
| 47 | |
| 48 | #define TCG_EV_NO_ACTION 3 |
| 49 | #define TCG_SPECID_SIG "Spec ID Event03" |
| 50 | |
| 51 | // At most one of these is non-NULL after tpm_init. tcg2 takes precedence |
| 52 | // since it's the more common case (real TPMs); the cc fallback is for |
| 53 | // confidential-computing platforms (TDX, SEV-SNP) without a discrete TPM. |
| 54 | static EFI_TCG2_PROTOCOL *tcg2 = NULL; |
| 55 | static EFI_CC_MEASUREMENT_PROTOCOL *cc = NULL; |
| 56 | |
| 57 | void tpm_init(void) { |
| 58 | EFI_GUID tcg2_guid = EFI_TCG2_PROTOCOL_GUID; |
| 59 | EFI_TCG2_PROTOCOL *tcg2_proto = NULL; |
| 60 | EFI_STATUS status = gBS->LocateProtocol(&tcg2_guid, NULL, (void **)&tcg2_proto); |
| 61 | if (status == EFI_SUCCESS && tcg2_proto != NULL) { |
| 62 | EFI_TCG2_BOOT_SERVICE_CAPABILITY cap; |
| 63 | memset(&cap, 0, sizeof(cap)); |
| 64 | cap.Size = sizeof(cap); |
| 65 | status = tcg2_proto->GetCapability(tcg2_proto, &cap); |
| 66 | if (status == EFI_SUCCESS && cap.TPMPresentFlag) { |
| 67 | tcg2 = tcg2_proto; |
| 68 | printv("tpm: TCG2 protocol located, TPM present (active PCR banks: %x)\n", |
| 69 | (uint32_t)cap.ActivePcrBanks); |
| 70 | return; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // No TCG2/TPM 2.0; fall back to the CC measurement protocol. |
| 75 | EFI_GUID cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID; |
| 76 | EFI_CC_MEASUREMENT_PROTOCOL *cc_proto = NULL; |
| 77 | status = gBS->LocateProtocol(&cc_guid, NULL, (void **)&cc_proto); |
| 78 | if (status == EFI_SUCCESS && cc_proto != NULL) { |
| 79 | EFI_CC_BOOT_SERVICE_CAPABILITY cap; |
| 80 | memset(&cap, 0, sizeof(cap)); |
| 81 | cap.Size = sizeof(cap); |
| 82 | status = cc_proto->GetCapability(cc_proto, &cap); |
| 83 | if (status == EFI_SUCCESS) { |
| 84 | cc = cc_proto; |
| 85 | const char *cc_name = "unknown"; |
| 86 | switch (cap.CcType.Type) { |
| 87 | case EFI_CC_TYPE_AMD_SEV: cc_name = "AMD SEV"; break; |
| 88 | case EFI_CC_TYPE_INTEL_TDX: cc_name = "Intel TDX"; break; |
| 89 | } |
| 90 | printv("tpm: CC measurement protocol located (type: %s)\n", cc_name); |
| 91 | return; |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | bool tpm_present(void) { |
| 97 | return tcg2 != NULL || cc != NULL; |
| 98 | } |
| 99 | |
| 100 | void tpm_measure(uint32_t pcr, uint32_t event_type, |
| 101 | const void *data, size_t data_size, |
| 102 | const char *desc_prefix, const char *desc_value) { |
| 103 | if (!measured_boot || data == NULL) { |
| 104 | return; |
| 105 | } |
| 106 | |
| 107 | size_t prefix_len = desc_prefix != NULL ? strlen(desc_prefix) : 0; |
| 108 | size_t value_len = desc_value != NULL ? strlen(desc_value) : 0; |
| 109 | size_t desc_len = prefix_len + value_len + 1; |
| 110 | |
| 111 | if (tcg2 != NULL) { |
| 112 | size_t event_size = offsetof(EFI_TCG2_EVENT, Event) + desc_len; |
| 113 | |
| 114 | EFI_TCG2_EVENT *event = ext_mem_alloc(event_size); |
| 115 | event->Size = (UINT32)event_size; |
| 116 | event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER); |
| 117 | event->Header.HeaderVersion = 1; |
| 118 | event->Header.PCRIndex = pcr; |
| 119 | event->Header.EventType = event_type; |
| 120 | if (prefix_len > 0) { |
| 121 | memcpy(event->Event, desc_prefix, prefix_len); |
| 122 | } |
| 123 | if (value_len > 0) { |
| 124 | memcpy(event->Event + prefix_len, desc_value, value_len); |
| 125 | } |
| 126 | |
| 127 | EFI_STATUS status = tcg2->HashLogExtendEvent( |
| 128 | tcg2, 0, |
| 129 | (EFI_PHYSICAL_ADDRESS)(uintptr_t)data, (UINT64)data_size, |
| 130 | event); |
| 131 | if (status != EFI_SUCCESS) { |
| 132 | printv("tpm: HashLogExtendEvent for PCR %u failed: %X\n", |
| 133 | pcr, (uint64_t)status); |
| 134 | } |
| 135 | |
| 136 | pmm_free(event, event_size); |
| 137 | } else if (cc != NULL) { |
| 138 | // CC platforms expose Memory Reference (MR) registers rather than |
| 139 | // PCRs. The protocol provides a translation from a requested PCR |
| 140 | // index to the platform's corresponding MR index. |
| 141 | EFI_CC_MR_INDEX mr_index; |
| 142 | EFI_STATUS status = cc->MapPcrToMrIndex(cc, pcr, &mr_index); |
| 143 | if (status != EFI_SUCCESS) { |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | size_t event_size = offsetof(EFI_CC_EVENT, Event) + desc_len; |
| 148 | |
| 149 | EFI_CC_EVENT *event = ext_mem_alloc(event_size); |
| 150 | event->Size = (UINT32)event_size; |
| 151 | event->Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER); |
| 152 | event->Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION; |
| 153 | event->Header.MrIndex = mr_index; |
| 154 | event->Header.EventType = event_type; |
| 155 | if (prefix_len > 0) { |
| 156 | memcpy(event->Event, desc_prefix, prefix_len); |
| 157 | } |
| 158 | if (value_len > 0) { |
| 159 | memcpy(event->Event + prefix_len, desc_value, value_len); |
| 160 | } |
| 161 | |
| 162 | status = cc->HashLogExtendEvent( |
| 163 | cc, 0, |
| 164 | (EFI_PHYSICAL_ADDRESS)(uintptr_t)data, (UINT64)data_size, |
| 165 | event); |
| 166 | if (status != EFI_SUCCESS) { |
| 167 | printv("tpm: CC HashLogExtendEvent for PCR %u (MR %u) failed: %X\n", |
| 168 | pcr, (uint32_t)mr_index, (uint64_t)status); |
| 169 | } |
| 170 | |
| 171 | pmm_free(event, event_size); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | void tpm_measure_path(uint32_t pcr, uint32_t event_type, |
| 176 | const char *desc_prefix, const char *path) { |
| 177 | if (!measured_boot || path == NULL) { |
| 178 | return; |
| 179 | } |
| 180 | |
| 181 | const char *hash_sep = strchr(path, '#'); |
| 182 | size_t path_len = hash_sep != NULL |
| 183 | ? (size_t)(hash_sep - path) |
| 184 | : strlen(path); |
| 185 | |
| 186 | // Static scratch matches uri.c's URI_BUF_SIZE; URIs longer than that |
| 187 | // already panic in uri_resolve(), so a too-long path here is a bug. |
| 188 | static char stripped[4096]; |
| 189 | if (path_len >= sizeof(stripped)) { |
| 190 | return; |
| 191 | } |
| 192 | memcpy(stripped, path, path_len); |
| 193 | stripped[path_len] = '\0'; |
| 194 | |
| 195 | tpm_measure(pcr, event_type, stripped, path_len, desc_prefix, stripped); |
| 196 | } |
| 197 | |
| 198 | uint32_t tpm_calc_event_size(const void *event_p, const void *header_p) { |
| 199 | const struct tpm_pcr_event2_head *event = event_p; |
| 200 | const struct tpm_pcr_event_v1_2 *event_header = header_p; |
| 201 | |
| 202 | static const uint8_t zero_digest[20] = {0}; |
| 203 | |
| 204 | if (event_header->pcr_idx != 0 |
| 205 | || event_header->event_type != TCG_EV_NO_ACTION |
| 206 | || memcmp(event_header->digest, zero_digest, sizeof(zero_digest)) != 0) { |
| 207 | return 0; |
| 208 | } |
| 209 | |
| 210 | const struct tpm_specid_event_head *efispecid = |
| 211 | (const struct tpm_specid_event_head *)event_header->event; |
| 212 | |
| 213 | if (memcmp(efispecid->signature, TCG_SPECID_SIG, sizeof(TCG_SPECID_SIG)) != 0 |
| 214 | || efispecid->num_algs == 0) { |
| 215 | return 0; |
| 216 | } |
| 217 | |
| 218 | const uint8_t *marker_start = (const uint8_t *)event_p; |
| 219 | const uint8_t *marker = marker_start |
| 220 | + sizeof(event->pcr_idx) |
| 221 | + sizeof(event->event_type) |
| 222 | + sizeof(event->count); |
| 223 | |
| 224 | for (uint32_t i = 0; i < event->count; i++) { |
| 225 | uint16_t halg; |
| 226 | memcpy(&halg, marker, sizeof(halg)); |
| 227 | marker += sizeof(halg); |
| 228 | |
| 229 | uint32_t j; |
| 230 | for (j = 0; j < efispecid->num_algs; j++) { |
| 231 | if (halg == efispecid->digest_sizes[j].alg_id) { |
| 232 | marker += efispecid->digest_sizes[j].digest_size; |
| 233 | break; |
| 234 | } |
| 235 | } |
| 236 | if (j == efispecid->num_algs) { |
| 237 | return 0; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | uint32_t trailing_event_size; |
| 242 | memcpy(&trailing_event_size, marker, sizeof(trailing_event_size)); |
| 243 | marker += sizeof(trailing_event_size) + trailing_event_size; |
| 244 | |
| 245 | if (event->event_type == 0 && trailing_event_size == 0) { |
| 246 | return 0; |
| 247 | } |
| 248 | |
| 249 | return (uint32_t)(marker - marker_start); |
| 250 | } |
| 251 | |
| 252 | static void *captured_log = NULL; |
| 253 | static size_t captured_log_size = 0; |
| 254 | static uint32_t captured_log_format = 0; |
| 255 | static bool capture_attempted = false; |
| 256 | |
| 257 | // Pull the firmware event log via GetEventLog and copy the raw event bytes |
| 258 | // into a bootloader-reclaimable buffer. Idempotent. Returns true if the |
| 259 | // captured state is valid. |
| 260 | static bool tpm_capture_event_log(void) { |
| 261 | if (capture_attempted) { |
| 262 | return captured_log != NULL; |
| 263 | } |
| 264 | capture_attempted = true; |
| 265 | |
| 266 | if (tcg2 == NULL && cc == NULL) { |
| 267 | return false; |
| 268 | } |
| 269 | |
| 270 | EFI_PHYSICAL_ADDRESS log_location = 0, log_last_entry = 0; |
| 271 | BOOLEAN truncated = FALSE; |
| 272 | uint32_t log_format = EFI_TCG2_EVENT_LOG_FORMAT_TCG_2; |
| 273 | EFI_STATUS status; |
| 274 | |
| 275 | if (tcg2 != NULL) { |
| 276 | status = tcg2->GetEventLog(tcg2, log_format, |
| 277 | &log_location, &log_last_entry, &truncated); |
| 278 | if (status != EFI_SUCCESS || log_location == 0) { |
| 279 | log_format = EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2; |
| 280 | status = tcg2->GetEventLog(tcg2, log_format, |
| 281 | &log_location, &log_last_entry, &truncated); |
| 282 | if (status != EFI_SUCCESS || log_location == 0) { |
| 283 | return false; |
| 284 | } |
| 285 | } |
| 286 | } else { |
| 287 | // CC measurement protocol. Only the TCG 2.0 log format is defined. |
| 288 | log_format = EFI_CC_EVENT_LOG_FORMAT_TCG_2; |
| 289 | status = cc->GetEventLog(cc, log_format, |
| 290 | &log_location, &log_last_entry, &truncated); |
| 291 | if (status != EFI_SUCCESS || log_location == 0) { |
| 292 | return false; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | uint32_t log_size = 0; |
| 297 | if (log_last_entry != 0) { |
| 298 | uint32_t last_entry_size = 0; |
| 299 | // The first entry of a TCG 2.0 log is itself a v1.2-format spec-ID |
| 300 | // event; only entries after it follow the crypto-agile layout. |
| 301 | if (log_format > EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 |
| 302 | && log_last_entry != log_location) { |
| 303 | last_entry_size = tpm_calc_event_size( |
| 304 | (void *)(uintptr_t)log_last_entry, |
| 305 | (void *)(uintptr_t)log_location); |
| 306 | } else { |
| 307 | const struct tpm_pcr_event_v1_2 *e = |
| 308 | (const struct tpm_pcr_event_v1_2 *)(uintptr_t)log_last_entry; |
| 309 | last_entry_size = sizeof(struct tpm_pcr_event_v1_2) + e->event_size; |
| 310 | } |
| 311 | log_size = (uint32_t)(log_last_entry - log_location) + last_entry_size; |
| 312 | } |
| 313 | |
| 314 | void *log_bytes = NULL; |
| 315 | if (log_size > 0) { |
| 316 | log_bytes = ext_mem_alloc(log_size); |
| 317 | memcpy(log_bytes, (void *)(uintptr_t)log_location, log_size); |
| 318 | } |
| 319 | |
| 320 | captured_log = log_bytes; |
| 321 | captured_log_size = log_size; |
| 322 | captured_log_format = log_format; |
| 323 | return true; |
| 324 | } |
| 325 | |
| 326 | bool tpm_get_event_log(uint32_t *format, void **address, size_t *size) { |
| 327 | if (!tpm_capture_event_log()) { |
| 328 | return false; |
| 329 | } |
| 330 | |
| 331 | *format = captured_log_format; |
| 332 | *address = captured_log; |
| 333 | *size = captured_log_size; |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | void tpm_release_event_log(void) { |
| 338 | if (captured_log != NULL) { |
| 339 | pmm_free(captured_log, captured_log_size); |
| 340 | captured_log = NULL; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | void *tpm_get_final_events_table(void) { |
| 345 | EFI_GUID guid; |
| 346 | if (tcg2 != NULL) { |
| 347 | EFI_GUID tcg2_guid = EFI_TCG2_FINAL_EVENTS_TABLE_GUID; |
| 348 | guid = tcg2_guid; |
| 349 | } else if (cc != NULL) { |
| 350 | EFI_GUID cc_guid = EFI_CC_FINAL_EVENTS_TABLE_GUID; |
| 351 | guid = cc_guid; |
| 352 | } else { |
| 353 | return NULL; |
| 354 | } |
| 355 | |
| 356 | for (UINTN i = 0; i < gST->NumberOfTableEntries; i++) { |
| 357 | if (memcmp(&gST->ConfigurationTable[i].VendorGuid, |
| 358 | &guid, sizeof(EFI_GUID)) == 0) { |
| 359 | return gST->ConfigurationTable[i].VendorTable; |
| 360 | } |
| 361 | } |
| 362 | return NULL; |
| 363 | } |
| 364 | |
| 365 | #endif |