mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-04-29 22:59:36 +00:00
471 lines
12 KiB
C
471 lines
12 KiB
C
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
#include "shim.h"
|
|
|
|
typedef struct {
|
|
CHAR16 *VariableName;
|
|
EFI_GUID *VendorGuid;
|
|
VOID *Data;
|
|
UINTN Size;
|
|
} VARIABLE_RECORD;
|
|
|
|
UINTN measuredcount = 0;
|
|
VARIABLE_RECORD *measureddata = NULL;
|
|
static BOOLEAN tpm_defective = FALSE;
|
|
static BOOLEAN log_full_already_warned = FALSE;
|
|
|
|
static BOOLEAN tpm_present(efi_tpm_protocol_t *tpm)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
TCG_EFI_BOOT_SERVICE_CAPABILITY caps;
|
|
UINT32 flags;
|
|
EFI_PHYSICAL_ADDRESS eventlog, lastevent;
|
|
|
|
if (tpm_defective)
|
|
return FALSE;
|
|
|
|
caps.Size = (UINT8)sizeof(caps);
|
|
efi_status = tpm->status_check(tpm, &caps, &flags,
|
|
&eventlog, &lastevent);
|
|
if (EFI_ERROR(efi_status) ||
|
|
caps.TPMDeactivatedFlag || !caps.TPMPresentFlag)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static EFI_STATUS tpm2_get_caps(efi_tpm2_protocol_t *tpm,
|
|
EFI_TCG2_BOOT_SERVICE_CAPABILITY *caps,
|
|
BOOLEAN *old_caps)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
|
|
caps->Size = (UINT8)sizeof(*caps);
|
|
|
|
efi_status = tpm->get_capability(tpm, caps);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
if (caps->StructureVersion.Major == 1 &&
|
|
caps->StructureVersion.Minor == 0)
|
|
*old_caps = TRUE;
|
|
else
|
|
*old_caps = FALSE;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static BOOLEAN tpm2_present(EFI_TCG2_BOOT_SERVICE_CAPABILITY *caps,
|
|
BOOLEAN old_caps)
|
|
{
|
|
TREE_BOOT_SERVICE_CAPABILITY *caps_1_0;
|
|
|
|
if (old_caps) {
|
|
caps_1_0 = (TREE_BOOT_SERVICE_CAPABILITY *)caps;
|
|
if (caps_1_0->TrEEPresentFlag)
|
|
return TRUE;
|
|
}
|
|
|
|
if (caps->TPMPresentFlag)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static EFI_STATUS tpm_locate_protocol(efi_tpm_protocol_t **tpm,
|
|
efi_tpm2_protocol_t **tpm2,
|
|
BOOLEAN *old_caps_p,
|
|
EFI_TCG2_BOOT_SERVICE_CAPABILITY *capsp)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
|
|
*tpm = NULL;
|
|
*tpm2 = NULL;
|
|
efi_status = LibLocateProtocol(&EFI_TPM2_GUID, (VOID **)tpm2);
|
|
/* TPM 2.0 */
|
|
if (!EFI_ERROR(efi_status)) {
|
|
BOOLEAN old_caps;
|
|
EFI_TCG2_BOOT_SERVICE_CAPABILITY caps;
|
|
|
|
efi_status = tpm2_get_caps(*tpm2, &caps, &old_caps);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
if (tpm2_present(&caps, old_caps)) {
|
|
if (old_caps_p)
|
|
*old_caps_p = old_caps;
|
|
if (capsp)
|
|
memcpy(capsp, &caps, sizeof(caps));
|
|
return EFI_SUCCESS;
|
|
}
|
|
} else {
|
|
efi_status = LibLocateProtocol(&EFI_TPM_GUID, (VOID **)tpm);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
if (tpm_present(*tpm))
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
static void warn_first_log_full(void)
|
|
{
|
|
if (!log_full_already_warned) {
|
|
perror(L"TPM extend operation occurred, but the event could"
|
|
" not be written to one or more event logs. Applications"
|
|
" reliant on a valid event log will not function.\n");
|
|
log_full_already_warned = TRUE;
|
|
}
|
|
}
|
|
|
|
static EFI_STATUS cc_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size,
|
|
UINT8 pcr, const CHAR8 *log, UINTN logsize,
|
|
UINT32 type, BOOLEAN is_pe_image)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
EFI_CC_EVENT *event;
|
|
efi_cc_protocol_t *cc;
|
|
EFI_CC_MR_INDEX mr;
|
|
uint64_t flags = is_pe_image ? EFI_CC_FLAG_PE_COFF_IMAGE : 0;
|
|
|
|
efi_status = LibLocateProtocol(&EFI_CC_MEASUREMENT_PROTOCOL_GUID,
|
|
(VOID **)&cc);
|
|
if (EFI_ERROR(efi_status) || !cc)
|
|
return EFI_SUCCESS;
|
|
|
|
efi_status = cc->map_pcr_to_mr_index(cc, pcr, &mr);
|
|
if (EFI_ERROR(efi_status))
|
|
return EFI_NOT_FOUND;
|
|
|
|
UINTN event_size = sizeof(*event) - sizeof(event->Event) + logsize;
|
|
|
|
event = AllocatePool(event_size);
|
|
if (!event) {
|
|
perror(L"Unable to allocate event structure\n");
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
event->Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER);
|
|
event->Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION;
|
|
event->Header.MrIndex = mr;
|
|
event->Header.EventType = type;
|
|
event->Size = event_size;
|
|
CopyMem(event->Event, (VOID *)log, logsize);
|
|
efi_status = cc->hash_log_extend_event(cc, flags, buf, (UINT64)size,
|
|
event);
|
|
/* Per spec: The extend operation occurred, but the event could
|
|
* not be written to one or more event logs. We can still safely
|
|
* boot in this case, but also show a warning to let the user know.
|
|
*/
|
|
if (efi_status == EFI_VOLUME_FULL) {
|
|
warn_first_log_full();
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
FreePool(event);
|
|
return efi_status;
|
|
}
|
|
|
|
static EFI_STATUS tpm_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size,
|
|
UINT8 pcr, const CHAR8 *log, UINTN logsize,
|
|
UINT32 type, CHAR8 *hash)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
efi_tpm_protocol_t *tpm;
|
|
efi_tpm2_protocol_t *tpm2;
|
|
BOOLEAN old_caps;
|
|
EFI_TCG2_BOOT_SERVICE_CAPABILITY caps;
|
|
|
|
/* CC guest like TDX or SEV will measure the buffer and log the event,
|
|
extend the result into a specific CC MR like TCG's PCR. It could
|
|
coexists with TCG's TPM 1.2 and TPM 2.
|
|
*/
|
|
efi_status = cc_log_event_raw(buf, size, pcr, log, logsize, type,
|
|
(hash != NULL));
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
efi_status = tpm_locate_protocol(&tpm, &tpm2, &old_caps, &caps);
|
|
if (EFI_ERROR(efi_status)) {
|
|
#ifdef REQUIRE_TPM
|
|
perror(L"TPM logging failed: %r\n", efi_status);
|
|
return efi_status;
|
|
#else
|
|
if (efi_status != EFI_NOT_FOUND) {
|
|
perror(L"TPM logging failed: %r\n", efi_status);
|
|
return efi_status;
|
|
}
|
|
#endif
|
|
} else if (tpm2) {
|
|
EFI_TCG2_EVENT *event;
|
|
UINTN event_size = sizeof(*event) - sizeof(event->Event) +
|
|
logsize;
|
|
|
|
event = AllocatePool(event_size);
|
|
if (!event) {
|
|
perror(L"Unable to allocate event structure\n");
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER);
|
|
event->Header.HeaderVersion = 1;
|
|
event->Header.PCRIndex = pcr;
|
|
event->Header.EventType = type;
|
|
event->Size = event_size;
|
|
CopyMem(event->Event, (VOID *)log, logsize);
|
|
if (hash) {
|
|
/* TPM 2 systems will generate the appropriate hash
|
|
themselves if we pass PE_COFF_IMAGE. In case that
|
|
fails we fall back to measuring without it.
|
|
*/
|
|
efi_status = tpm2->hash_log_extend_event(tpm2,
|
|
PE_COFF_IMAGE, buf, (UINT64) size, event);
|
|
if (efi_status == EFI_VOLUME_FULL) {
|
|
warn_first_log_full();
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (!hash || EFI_ERROR(efi_status)) {
|
|
efi_status = tpm2->hash_log_extend_event(tpm2,
|
|
0, buf, (UINT64) size, event);
|
|
if (efi_status == EFI_VOLUME_FULL) {
|
|
warn_first_log_full();
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
}
|
|
FreePool(event);
|
|
return efi_status;
|
|
} else if (tpm) {
|
|
TCG_PCR_EVENT *event;
|
|
UINT32 eventnum = 0;
|
|
EFI_PHYSICAL_ADDRESS lastevent;
|
|
|
|
efi_status = LibLocateProtocol(&EFI_TPM_GUID, (VOID **)&tpm);
|
|
if (EFI_ERROR(efi_status))
|
|
return EFI_SUCCESS;
|
|
|
|
if (!tpm_present(tpm))
|
|
return EFI_SUCCESS;
|
|
|
|
event = AllocatePool(sizeof(*event) + logsize);
|
|
|
|
if (!event) {
|
|
perror(L"Unable to allocate event structure\n");
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
event->PCRIndex = pcr;
|
|
event->EventType = type;
|
|
event->EventSize = logsize;
|
|
CopyMem(event->Event, (VOID *)log, logsize);
|
|
if (hash) {
|
|
/* TPM 1.2 devices require us to pass the Authenticode
|
|
hash rather than allowing the firmware to attempt
|
|
to calculate it */
|
|
CopyMem(event->digest, hash, sizeof(event->digest));
|
|
efi_status = tpm->log_extend_event(tpm, 0, 0,
|
|
TPM_ALG_SHA, event, &eventnum, &lastevent);
|
|
if (efi_status == EFI_VOLUME_FULL) {
|
|
warn_first_log_full();
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
} else {
|
|
efi_status = tpm->log_extend_event(tpm, buf,
|
|
(UINT64)size, TPM_ALG_SHA, event, &eventnum,
|
|
&lastevent);
|
|
if (efi_status == EFI_VOLUME_FULL) {
|
|
warn_first_log_full();
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
}
|
|
if (efi_status == EFI_UNSUPPORTED) {
|
|
perror(L"Could not write TPM event: %r. Considering "
|
|
"the TPM as defective.\n", efi_status);
|
|
tpm_defective = TRUE;
|
|
efi_status = EFI_SUCCESS;
|
|
}
|
|
FreePool(event);
|
|
return efi_status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS tpm_log_event(EFI_PHYSICAL_ADDRESS buf, UINTN size, UINT8 pcr,
|
|
const CHAR8 *description)
|
|
{
|
|
return tpm_log_event_raw(buf, size, pcr, description,
|
|
strlen(description) + 1, EV_IPL, NULL);
|
|
}
|
|
|
|
EFI_STATUS tpm_log_pe(EFI_PHYSICAL_ADDRESS buf, UINTN size,
|
|
EFI_PHYSICAL_ADDRESS addr, EFI_DEVICE_PATH *path,
|
|
UINT8 *sha1hash, UINT8 pcr)
|
|
{
|
|
EFI_IMAGE_LOAD_EVENT *ImageLoad = NULL;
|
|
EFI_STATUS efi_status;
|
|
UINTN path_size = 0;
|
|
|
|
if (path)
|
|
path_size = DevicePathSize(path);
|
|
|
|
ImageLoad = AllocateZeroPool(sizeof(*ImageLoad) + path_size);
|
|
if (!ImageLoad) {
|
|
perror(L"Unable to allocate image load event structure\n");
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
ImageLoad->ImageLocationInMemory = buf;
|
|
ImageLoad->ImageLengthInMemory = size;
|
|
ImageLoad->ImageLinkTimeAddress = addr;
|
|
|
|
if (path_size > 0) {
|
|
CopyMem(ImageLoad->DevicePath, path, path_size);
|
|
ImageLoad->LengthOfDevicePath = path_size;
|
|
}
|
|
|
|
efi_status = tpm_log_event_raw(buf, size, pcr, (CHAR8 *)ImageLoad,
|
|
sizeof(*ImageLoad) + path_size,
|
|
EV_EFI_BOOT_SERVICES_APPLICATION,
|
|
(CHAR8 *)sha1hash);
|
|
FreePool(ImageLoad);
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
typedef struct {
|
|
EFI_GUID VariableName;
|
|
UINT64 UnicodeNameLength;
|
|
UINT64 VariableDataLength;
|
|
CHAR16 UnicodeName[1];
|
|
INT8 VariableData[1];
|
|
} __attribute__ ((packed)) EFI_VARIABLE_DATA_TREE;
|
|
|
|
static BOOLEAN tpm_data_measured(CHAR16 *VarName, EFI_GUID VendorGuid, UINTN VarSize, VOID *VarData)
|
|
{
|
|
UINTN i;
|
|
|
|
for (i=0; i<measuredcount; i++) {
|
|
if ((StrCmp (VarName, measureddata[i].VariableName) == 0) &&
|
|
(CompareGuid (&VendorGuid, measureddata[i].VendorGuid) == 0) &&
|
|
(VarSize == measureddata[i].Size) &&
|
|
(CompareMem (VarData, measureddata[i].Data, VarSize) == 0)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static EFI_STATUS tpm_record_data_measurement(CHAR16 *VarName, EFI_GUID VendorGuid, UINTN VarSize, VOID *VarData)
|
|
{
|
|
if (measureddata == NULL) {
|
|
measureddata = AllocatePool(sizeof(*measureddata));
|
|
} else {
|
|
measureddata = ReallocatePool(measureddata, measuredcount * sizeof(*measureddata),
|
|
(measuredcount + 1) * sizeof(*measureddata));
|
|
}
|
|
|
|
if (measureddata == NULL)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
measureddata[measuredcount].VariableName = AllocatePool(StrSize(VarName));
|
|
measureddata[measuredcount].VendorGuid = AllocatePool(sizeof(EFI_GUID));
|
|
measureddata[measuredcount].Data = AllocatePool(VarSize);
|
|
|
|
if (measureddata[measuredcount].VariableName == NULL ||
|
|
measureddata[measuredcount].VendorGuid == NULL ||
|
|
measureddata[measuredcount].Data == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
StrCpy(measureddata[measuredcount].VariableName, VarName);
|
|
CopyMem(measureddata[measuredcount].VendorGuid, &VendorGuid, sizeof(EFI_GUID));
|
|
CopyMem(measureddata[measuredcount].Data, VarData, VarSize);
|
|
measureddata[measuredcount].Size = VarSize;
|
|
measuredcount++;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS tpm_measure_variable(CHAR16 *VarName, EFI_GUID VendorGuid, UINTN VarSize, VOID *VarData)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
UINTN VarNameLength;
|
|
EFI_VARIABLE_DATA_TREE *VarLog;
|
|
UINT32 VarLogSize;
|
|
|
|
/* Don't measure something that we've already measured */
|
|
if (tpm_data_measured(VarName, VendorGuid, VarSize, VarData))
|
|
return EFI_SUCCESS;
|
|
|
|
VarNameLength = StrLen (VarName);
|
|
VarLogSize = (UINT32)(sizeof (*VarLog) +
|
|
VarNameLength * sizeof (*VarName) +
|
|
VarSize -
|
|
sizeof (VarLog->UnicodeName) -
|
|
sizeof (VarLog->VariableData));
|
|
|
|
VarLog = (EFI_VARIABLE_DATA_TREE *) AllocateZeroPool (VarLogSize);
|
|
if (VarLog == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
CopyMem (&VarLog->VariableName, &VendorGuid,
|
|
sizeof(VarLog->VariableName));
|
|
VarLog->UnicodeNameLength = VarNameLength;
|
|
VarLog->VariableDataLength = VarSize;
|
|
CopyMem (VarLog->UnicodeName, VarName,
|
|
VarNameLength * sizeof (*VarName));
|
|
CopyMem ((CHAR16 *)VarLog->UnicodeName + VarNameLength, VarData,
|
|
VarSize);
|
|
|
|
efi_status = tpm_log_event_raw((EFI_PHYSICAL_ADDRESS)(intptr_t)VarLog,
|
|
VarLogSize, 7, (CHAR8 *)VarLog, VarLogSize,
|
|
EV_EFI_VARIABLE_AUTHORITY, NULL);
|
|
|
|
FreePool(VarLog);
|
|
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
return tpm_record_data_measurement(VarName, VendorGuid, VarSize,
|
|
VarData);
|
|
}
|
|
|
|
EFI_STATUS
|
|
fallback_should_prefer_reset(void)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
efi_tpm_protocol_t *tpm;
|
|
efi_tpm2_protocol_t *tpm2;
|
|
|
|
efi_status = tpm_locate_protocol(&tpm, &tpm2, NULL, NULL);
|
|
if (EFI_ERROR(efi_status))
|
|
return EFI_NOT_FOUND;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
#ifdef SHIM_UNIT_TEST
|
|
static void DESTRUCTOR
|
|
tpm_clean_up_measurements(void)
|
|
{
|
|
for (UINTN i = 0; i < measuredcount; i++) {
|
|
VARIABLE_RECORD *vr = &measureddata[i];
|
|
|
|
if (vr->VariableName)
|
|
FreePool(vr->VariableName);
|
|
if (vr->VendorGuid)
|
|
FreePool(vr->VendorGuid);
|
|
if (vr->Data)
|
|
FreePool(vr->Data);
|
|
}
|
|
if (measureddata)
|
|
FreePool(measureddata);
|
|
|
|
measuredcount = 0;
|
|
measureddata = NULL;
|
|
}
|
|
#endif
|