blob: 8714dd8fed54b180ff658a65894f887f03f20e67 [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Functions for querying, manipulating and locking rollback indices
* stored in the TPM NVRAM.
*/
#include <console/console.h>
#include <security/tpm/tspi.h>
#include <security/vboot/antirollback.h>
#include <vb2_api.h>
#include "secdata_tpm_private.h"
tpm_result_t antirollback_read_space_kernel(struct vb2_context *ctx)
{
if (tlcl_get_family() == TPM_1) {
/*
* Before reading the kernel space, verify its permissions. If
* the kernel space has the wrong permission, we give up. This
* will need to be fixed by the recovery kernel. We will have
* to worry about this because at any time (even with PP turned
* off) the TPM owner can remove and redefine a PP-protected
* space (but not write to it).
*/
uint32_t perms;
RETURN_ON_FAILURE(tlcl1_get_permissions(KERNEL_NV_INDEX, &perms));
if (perms != TPM_NV_PER_PPWRITE) {
printk(BIOS_ERR,
"TPM: invalid secdata_kernel permissions\n");
return TPM_CB_CORRUPTED_STATE;
}
}
uint8_t size = VB2_SECDATA_KERNEL_SIZE;
tpm_result_t rc;
/* Start with the version 1.0 size used by all modern Cr50/Ti50 boards. */
rc = tlcl_read(KERNEL_NV_INDEX, ctx->secdata_kernel, size);
if (rc == TPM_CB_RANGE) {
/* Fallback to version 0.2(minimum) size and re-read. */
VBDEBUG("Antirollback: NV read out of range, trying min size\n");
size = VB2_SECDATA_KERNEL_MIN_SIZE;
rc = tlcl_read(KERNEL_NV_INDEX, ctx->secdata_kernel, size);
}
RETURN_ON_FAILURE(rc);
if (vb2api_secdata_kernel_check(ctx, &size) == VB2_ERROR_SECDATA_KERNEL_INCOMPLETE)
/* Re-read. vboot will run the check and handle errors. */
RETURN_ON_FAILURE(tlcl_read(KERNEL_NV_INDEX, ctx->secdata_kernel, size));
return TPM_SUCCESS;
}
tpm_result_t safe_write(uint32_t index, const void *data, uint32_t length)
{
tpm_result_t rc = tlcl_write(index, data, length);
if (tlcl_get_family() == TPM_1 && rc == TPM_MAXNVWRITES) {
/**
* Clear the TPM on write error due to hitting the 64-write
* limit. This can only happen when the TPM is unowned, so it
* is OK to clear it (and we really have no choice). This is
* not expected to happen frequently, but it could happen.
*/
RETURN_ON_FAILURE(tpm_clear_and_reenable());
rc = tlcl_write(index, data, length);
}
return rc;
}
static uint32_t _factory_initialize_tpm(struct vb2_context *ctx)
{
if (tlcl_get_family() == TPM_1)
return factory_initialize_tpm1(ctx);
if (tlcl_get_family() == TPM_2)
return factory_initialize_tpm2(ctx);
return TPM_CB_CORRUPTED_STATE;
}
uint32_t antirollback_lock_space_firmware(void)
{
if (tlcl_get_family() == TPM_1)
return tlcl1_set_global_lock();
if (tlcl_get_family() == TPM_2)
return tlcl2_lock_nv_write(FIRMWARE_NV_INDEX);
return TPM_CB_CORRUPTED_STATE;
}
/**
* Perform one-time initializations.
*
* Create the NVRAM spaces, and set their initial values as needed. Sets the
* nvLocked bit and ensures the physical presence command is enabled and
* locked.
*/
static tpm_result_t factory_initialize_tpm(struct vb2_context *ctx)
{
tpm_result_t rc;
VBDEBUG("TPM: factory initialization\n");
/*
* Do a full test. This only happens the first time the device is
* turned on in the factory, so performance is not an issue. This is
* almost certainly not necessary, but it gives us more confidence
* about some code paths below that are difficult to
* test---specifically the ones that set lifetime flags, and are only
* executed once per physical TPM.
*/
rc = tlcl_self_test_full();
if (rc != TPM_SUCCESS)
return rc;
rc = _factory_initialize_tpm(ctx);
if (rc != TPM_SUCCESS)
return rc;
/* _factory_initialize_tpm() writes initial secdata values to TPM
immediately, so let vboot know that it's up to date now. */
ctx->flags &= ~(VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED |
VB2_CONTEXT_SECDATA_KERNEL_CHANGED);
VBDEBUG("TPM: factory initialization successful\n");
return TPM_SUCCESS;
}
tpm_result_t antirollback_read_space_firmware(struct vb2_context *ctx)
{
tpm_result_t rc;
rc = tlcl_read(FIRMWARE_NV_INDEX, ctx->secdata_firmware, VB2_SECDATA_FIRMWARE_SIZE);
if (rc == TPM_BADINDEX) {
/* This seems the first time we've run. Initialize the TPM. */
VBDEBUG("TPM: Not initialized yet\n");
RETURN_ON_FAILURE(factory_initialize_tpm(ctx));
} else if (rc != TPM_SUCCESS) {
printk(BIOS_ERR, "TPM: Failed to read firmware space: %#x\n", rc);
return TPM_CB_CORRUPTED_STATE;
}
return rc;
}
tpm_result_t antirollback_write_space_firmware(struct vb2_context *ctx)
{
if (CONFIG(TPM_GOOGLE_IMMEDIATELY_COMMIT_FW_SECDATA))
tlcl_cr50_enable_nvcommits();
return safe_write(FIRMWARE_NV_INDEX, ctx->secdata_firmware,
VB2_SECDATA_FIRMWARE_SIZE);
}
tpm_result_t antirollback_write_space_kernel(struct vb2_context *ctx)
{
/* Learn the expected size. */
uint8_t size = VB2_SECDATA_KERNEL_MIN_SIZE;
vb2api_secdata_kernel_check(ctx, &size);
/*
* Ensure that the TPM actually commits our changes to NVMEN in case
* there is a power loss or other unexpected event. The AP does not
* write to the TPM during normal boot flow; it only writes during
* recovery, software sync, or other special boot flows. When the AP
* wants to write, it is imporant to actually commit changes.
*/
if (CONFIG(TPM_GOOGLE_IMMEDIATELY_COMMIT_FW_SECDATA))
tlcl_cr50_enable_nvcommits();
return safe_write(KERNEL_NV_INDEX, ctx->secdata_kernel, size);
}
vb2_error_t vb2ex_tpm_clear_owner(struct vb2_context *ctx)
{
printk(BIOS_INFO, "Clearing TPM owner\n");
return tpm_clear_and_reenable() == TPM_SUCCESS ? VB2_SUCCESS : VB2_ERROR_EX_TPM_CLEAR_OWNER;
}