/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2018 Free Software Foundation, Inc.
*
* VAS_EBOOT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VAS_EBOOT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VAS_EBOOT. If not, see .
*
* EFI TPM support code.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef TCG_PCR_EVENT VasEBoot_tpm_event_t;
static VasEBoot_guid_t tpm_guid = EFI_TPM_GUID;
static VasEBoot_guid_t tpm2_guid = EFI_TPM2_GUID;
static VasEBoot_guid_t cc_measurement_guid = VAS_EBOOT_EFI_CC_MEASUREMENT_PROTOCOL_GUID;
static VasEBoot_efi_handle_t *VasEBoot_tpm_handle;
static VasEBoot_uint8_t VasEBoot_tpm_version;
static VasEBoot_int8_t tpm1_present = -1;
static VasEBoot_int8_t tpm2_present = -1;
static VasEBoot_efi_int64_t tpm2_active_pcr_banks = -1;
static VasEBoot_efi_boolean_t
VasEBoot_tpm1_present (VasEBoot_efi_tpm_protocol_t *tpm)
{
VasEBoot_efi_status_t status;
TCG_EFI_BOOT_SERVICE_CAPABILITY caps;
VasEBoot_uint32_t flags;
VasEBoot_efi_physical_address_t eventlog, lastevent;
if (tpm1_present != -1)
return (VasEBoot_efi_boolean_t) tpm1_present;
caps.Size = (VasEBoot_uint8_t) sizeof (caps);
status = tpm->status_check (tpm, &caps, &flags, &eventlog, &lastevent);
if (status != VAS_EBOOT_EFI_SUCCESS || caps.TPMDeactivatedFlag
|| !caps.TPMPresentFlag)
tpm1_present = 0;
else
tpm1_present = 1;
VasEBoot_dprintf ("tpm", "tpm1%s present\n", tpm1_present ? "" : " NOT");
return (VasEBoot_efi_boolean_t) tpm1_present;
}
static VasEBoot_efi_boolean_t
VasEBoot_tpm2_present (VasEBoot_efi_tpm2_protocol_t *tpm)
{
VasEBoot_efi_status_t status;
EFI_TCG2_BOOT_SERVICE_CAPABILITY caps;
caps.Size = (VasEBoot_uint8_t) sizeof (caps);
if (tpm2_present != -1)
return (VasEBoot_efi_boolean_t) tpm2_present;
status = tpm->get_capability (tpm, &caps);
if (status != VAS_EBOOT_EFI_SUCCESS || !caps.TPMPresentFlag)
tpm2_present = 0;
else
tpm2_present = 1;
VasEBoot_dprintf ("tpm", "tpm2%s present\n", tpm2_present ? "" : " NOT");
return (VasEBoot_efi_boolean_t) tpm2_present;
}
static VasEBoot_efi_boolean_t
VasEBoot_tpm_handle_find (VasEBoot_efi_handle_t *tpm_handle,
VasEBoot_efi_uint8_t *protocol_version)
{
VasEBoot_efi_handle_t *handles;
VasEBoot_efi_uintn_t num_handles;
if (VasEBoot_tpm_handle != NULL)
{
*tpm_handle = VasEBoot_tpm_handle;
*protocol_version = VasEBoot_tpm_version;
return 1;
}
handles = VasEBoot_efi_locate_handle (VAS_EBOOT_EFI_BY_PROTOCOL, &tpm_guid, NULL,
&num_handles);
if (handles && num_handles > 0)
{
VasEBoot_tpm_handle = handles[0];
*tpm_handle = handles[0];
VasEBoot_tpm_version = 1;
*protocol_version = 1;
VasEBoot_dprintf ("tpm", "TPM handle Found, version: 1\n");
VasEBoot_free (handles);
return 1;
}
handles = VasEBoot_efi_locate_handle (VAS_EBOOT_EFI_BY_PROTOCOL, &tpm2_guid, NULL,
&num_handles);
if (handles && num_handles > 0)
{
VasEBoot_tpm_handle = handles[0];
*tpm_handle = handles[0];
VasEBoot_tpm_version = 2;
*protocol_version = 2;
VasEBoot_dprintf ("tpm", "TPM handle Found, version: 2\n");
VasEBoot_free (handles);
return 1;
}
return 0;
}
static VasEBoot_err_t
VasEBoot_efi_log_event_status (VasEBoot_efi_status_t status)
{
switch (status)
{
case VAS_EBOOT_EFI_SUCCESS:
return VAS_EBOOT_ERR_NONE;
case VAS_EBOOT_EFI_DEVICE_ERROR:
return VasEBoot_error (VAS_EBOOT_ERR_IO, N_("command failed"));
case VAS_EBOOT_EFI_INVALID_PARAMETER:
return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("invalid parameter"));
case VAS_EBOOT_EFI_BUFFER_TOO_SMALL:
return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("output buffer too small"));
case VAS_EBOOT_EFI_NOT_FOUND:
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, N_("TPM unavailable"));
default:
return VasEBoot_error (VasEBoot_is_tpm_fail_fatal () ? VAS_EBOOT_ERR_UNKNOWN_DEVICE : VAS_EBOOT_ERR_NONE, N_("unknown TPM error"));
}
}
static VasEBoot_err_t
VasEBoot_tpm1_log_event (VasEBoot_efi_handle_t tpm_handle, unsigned char *buf,
VasEBoot_size_t size, VasEBoot_uint8_t pcr,
const char *description)
{
VasEBoot_tpm_event_t *event;
VasEBoot_efi_status_t status;
VasEBoot_efi_tpm_protocol_t *tpm;
VasEBoot_efi_physical_address_t lastevent;
VasEBoot_uint32_t algorithm;
VasEBoot_uint32_t eventnum = 0;
tpm = VasEBoot_efi_open_protocol (tpm_handle, &tpm_guid,
VAS_EBOOT_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!VasEBoot_tpm1_present (tpm))
return 0;
event = VasEBoot_zalloc (sizeof (*event) + VasEBoot_strlen (description) + 1);
if (!event)
return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY,
N_("cannot allocate TPM event buffer"));
event->PCRIndex = pcr;
event->EventType = EV_IPL;
event->EventSize = VasEBoot_strlen (description) + 1;
VasEBoot_strcpy ((char *) event->Event, description);
algorithm = TCG_ALG_SHA;
status = tpm->log_extend_event (tpm, (VasEBoot_addr_t) buf, (VasEBoot_uint64_t) size,
algorithm, event, &eventnum, &lastevent);
VasEBoot_free (event);
return VasEBoot_efi_log_event_status (status);
}
static VasEBoot_err_t
VasEBoot_tpm2_log_event (VasEBoot_efi_handle_t tpm_handle, unsigned char *buf,
VasEBoot_size_t size, VasEBoot_uint8_t pcr,
const char *description)
{
EFI_TCG2_EVENT *event;
VasEBoot_efi_status_t status;
VasEBoot_efi_tpm2_protocol_t *tpm;
tpm = VasEBoot_efi_open_protocol (tpm_handle, &tpm2_guid,
VAS_EBOOT_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!VasEBoot_tpm2_present (tpm))
return 0;
event =
VasEBoot_zalloc (sizeof (EFI_TCG2_EVENT) + VasEBoot_strlen (description) + 1);
if (!event)
return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY,
N_("cannot allocate TPM event buffer"));
event->Header.HeaderSize = sizeof (EFI_TCG2_EVENT_HEADER);
event->Header.HeaderVersion = 1;
event->Header.PCRIndex = pcr;
event->Header.EventType = EV_IPL;
event->Size =
sizeof (*event) - sizeof (event->Event) + VasEBoot_strlen (description) + 1;
VasEBoot_strcpy ((char *) event->Event, description);
status = tpm->hash_log_extend_event (tpm, 0, (VasEBoot_addr_t) buf,
(VasEBoot_uint64_t) size, event);
VasEBoot_free (event);
return VasEBoot_efi_log_event_status (status);
}
static void
VasEBoot_cc_log_event (unsigned char *buf, VasEBoot_size_t size, VasEBoot_uint8_t pcr,
const char *description)
{
VasEBoot_efi_cc_event_t *event;
VasEBoot_efi_status_t status;
VasEBoot_efi_cc_protocol_t *cc;
VasEBoot_efi_cc_mr_index_t mr;
cc = VasEBoot_efi_locate_protocol (&cc_measurement_guid, NULL);
if (cc == NULL)
return;
status = cc->map_pcr_to_mr_index (cc, pcr, &mr);
if (status != VAS_EBOOT_EFI_SUCCESS)
{
VasEBoot_efi_log_event_status (status);
return;
}
event = VasEBoot_zalloc (sizeof (VasEBoot_efi_cc_event_t) +
VasEBoot_strlen (description) + 1);
if (event == NULL)
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, N_("cannot allocate CC event buffer"));
return;
}
event->Header.HeaderSize = sizeof (VasEBoot_efi_cc_event_header_t);
event->Header.HeaderVersion = VAS_EBOOT_EFI_CC_EVENT_HEADER_VERSION;
event->Header.MrIndex = mr;
event->Header.EventType = EV_IPL;
event->Size = sizeof (*event) + VasEBoot_strlen (description) + 1;
VasEBoot_strcpy ((char *) event->Event, description);
status = cc->hash_log_extend_event (cc, 0,
(VasEBoot_efi_physical_address_t)(VasEBoot_addr_t) buf,
(VasEBoot_efi_uint64_t) size, event);
VasEBoot_free (event);
if (status != VAS_EBOOT_EFI_SUCCESS)
VasEBoot_efi_log_event_status (status);
}
VasEBoot_err_t
VasEBoot_tpm_measure (unsigned char *buf, VasEBoot_size_t size, VasEBoot_uint8_t pcr,
const char *description)
{
VasEBoot_efi_handle_t tpm_handle;
VasEBoot_efi_uint8_t protocol_version;
VasEBoot_cc_log_event(buf, size, pcr, description);
if (!VasEBoot_tpm_handle_find (&tpm_handle, &protocol_version))
return 0;
VasEBoot_dprintf ("tpm", "log_event, pcr = %d, size = 0x%" PRIxVAS_EBOOT_SIZE ", %s\n",
pcr, size, description);
if (protocol_version == 1)
return VasEBoot_tpm1_log_event (tpm_handle, buf, size, pcr, description);
else
return VasEBoot_tpm2_log_event (tpm_handle, buf, size, pcr, description);
}
int
VasEBoot_tpm_present (void)
{
VasEBoot_efi_handle_t tpm_handle;
VasEBoot_efi_uint8_t protocol_version;
VasEBoot_efi_cc_protocol_t *cc;
/*
* When confidential computing measurement protocol is enabled
* we assume the TPM is present.
*/
cc = VasEBoot_efi_locate_protocol (&cc_measurement_guid, NULL);
if (cc != NULL)
return 1;
if (!VasEBoot_tpm_handle_find (&tpm_handle, &protocol_version))
return 0;
if (protocol_version == 1)
{
VasEBoot_efi_tpm_protocol_t *tpm;
tpm = VasEBoot_efi_open_protocol (tpm_handle, &tpm_guid,
VAS_EBOOT_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!tpm)
{
VasEBoot_dprintf ("tpm", "Cannot open TPM protocol\n");
return 0;
}
return VasEBoot_tpm1_present (tpm);
}
else
{
VasEBoot_efi_tpm2_protocol_t *tpm;
tpm = VasEBoot_efi_open_protocol (tpm_handle, &tpm2_guid,
VAS_EBOOT_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!tpm)
{
VasEBoot_dprintf ("tpm", "Cannot open TPM protocol\n");
return 0;
}
return VasEBoot_tpm2_present (tpm);
}
}
VasEBoot_uint32_t
VasEBoot_tpm2_active_pcr_banks (void)
{
EFI_TCG2_BOOT_SERVICE_CAPABILITY caps;
VasEBoot_efi_handle_t tpm_handle;
VasEBoot_efi_uint8_t protocol_version;
VasEBoot_efi_tpm2_protocol_t *tpm;
VasEBoot_efi_uint32_t active_pcr_banks;
VasEBoot_efi_status_t status;
if (tpm2_active_pcr_banks >= 0)
return (VasEBoot_uint32_t) tpm2_active_pcr_banks;
if (!VasEBoot_tpm_handle_find (&tpm_handle, &protocol_version))
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0);
if (protocol_version == 1)
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0); /* We report TPM2 status. */
tpm = VasEBoot_efi_open_protocol (tpm_handle, &tpm2_guid,
VAS_EBOOT_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (tpm == NULL)
{
VasEBoot_dprintf ("tpm", "Cannot open TPM2 protocol\n");
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0);
}
if (!VasEBoot_tpm2_present (tpm))
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0);
caps.Size = (VasEBoot_uint8_t) sizeof (caps);
status = tpm->get_capability (tpm, &caps);
if (status != VAS_EBOOT_EFI_SUCCESS)
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0);
if (caps.StructureVersion.Major < 1 ||
(caps.StructureVersion.Major == 1 && caps.StructureVersion.Minor < 1))
/* There's a working TPM2 but without querying protocol, let userspace figure it out. */
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = VAS_EBOOT_UINT_MAX);
status = tpm->get_active_pcr_banks (tpm, &active_pcr_banks);
if (status != VAS_EBOOT_EFI_SUCCESS)
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = 0); /* Assume none available if the call fails. */
return (VasEBoot_uint32_t) (tpm2_active_pcr_banks = active_pcr_banks);
}