blob: da581bdcf8f44581a9598e360e64ea600aca5bef [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Functions for querying, manipulating and locking rollback indices
* stored in the TPM NVRAM.
*/
#define LOG_CATEGORY LOGC_VBOOT
#include <common.h>
#include <log.h>
#include <tpm-common.h>
#include <tpm_api.h>
#include <cros/antirollback.h>
#include <cros/cros_common.h>
#include <cros/nvdata.h>
#include <cros/vboot.h>
static int read_space_firmware(struct vb2_context *ctx)
{
int ret;
ret = cros_nvdata_read_walk(CROS_NV_SECDATAF, ctx->secdata_firmware,
VB2_SECDATA_FIRMWARE_SIZE);
if (ret)
return log_msg_ret("read", ret);
return 0;
}
int antirollback_read_space_kernel(const struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
u8 size;
int ret;
if (tpm_is_v1(vboot->tpm)) {
/*
* 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).
*/
u32 perms;
ret = tpm_get_permissions(vboot->tpm, KERNEL_NV_INDEX, &perms);
if (ret)
return log_msg_retz("gperm", ret);
if (perms != TPM_NV_PER_PPWRITE) {
log_err("TPM: invalid secdata_kernel permissions %x\n",
perms);
return log_msg_ret("perm", -EBADFD);
}
}
size = VB2_SECDATA_KERNEL_MIN_SIZE;
ret = cros_nvdata_read_walk(CROS_NV_SECDATAK, ctx->secdata_kernel,
size);
if (ret)
return log_msg_ret("read1", ret);
if (vb2api_secdata_kernel_check(ctx, &size)
== VB2_ERROR_SECDATA_KERNEL_INCOMPLETE) {
/* Re-read. vboot will run the check and handle errors */
ret = cros_nvdata_read_walk(CROS_NV_SECDATAK,
ctx->secdata_kernel, size);
if (ret)
return log_msg_ret("read1", ret);
}
return 0;
}
static int read_space_mrc_hash(enum cros_nvdata_type type, u8 *data)
{
int ret;
ret = cros_nvdata_read_walk(type, data, HASH_NV_SIZE);
if (ret)
return log_msg_ret("read1", ret);
return 0;
}
/*
* This is used to initialize the TPM space for recovery hash after defining
* it. Since there is no data available to calculate hash at the point where TPM
* space is defined, initialize it to all 0s.
*/
static const uint8_t mrc_hash_data[HASH_NV_SIZE] = { };
/*
* Different sets of NVRAM space attributes apply to the "ro" spaces,
* i.e. those which should not be possible to delete or modify once
* the RO exits, and the rest of the NVRAM spaces.
*/
static const enum tpm_index_attrs ro_space_attributes =
TPMA_NV_PPWRITE |
TPMA_NV_AUTHREAD |
TPMA_NV_PPREAD |
TPMA_NV_PLATFORMCREATE |
TPMA_NV_WRITE_STCLEAR |
TPMA_NV_POLICY_DELETE;
static const u32 rw_space_attributes =
TPMA_NV_PPWRITE |
TPMA_NV_AUTHREAD |
TPMA_NV_PPREAD |
TPMA_NV_PLATFORMCREATE;
static const u32 fwmp_attr =
TPMA_NV_PLATFORMCREATE |
TPMA_NV_OWNERWRITE |
TPMA_NV_AUTHREAD |
TPMA_NV_PPREAD |
TPMA_NV_PPWRITE;
/*
* This policy digest was obtained using TPM2_PolicyOR on 3 digests
* corresponding to a sequence of
* -) TPM2_PolicyCommandCode(TPM_CC_NV_UndefineSpaceSpecial),
* -) TPM2_PolicyPCR(PCR0, <extended_value>).
* where <extended value> is
* 1) all zeros = initial, unextended state:
* - Value to extend to initial PCR0:
* <none>
* - Resulting PCR0:
* 0000000000000000000000000000000000000000000000000000000000000000
* - Policy digest for PolicyCommandCode + PolicyPCR:
* 4B44FC4192DB5AD7167E0135708FD374890A06BFB56317DF01F24F2226542A3F
* 2) result of extending (SHA1(0x00|0x01|0x00) | 00s to SHA256 size)
* - Value to extend to initial PCR0:
* 62571891215b4efc1ceab744ce59dd0b66ea6f73000000000000000000000000
* - Resulting PCR0:
* 9F9EA866D3F34FE3A3112AE9CB1FBABC6FFE8CD261D42493BC6842A9E4F93B3D
* - Policy digest for PolicyCommandCode + PolicyPCR:
* CB5C8014E27A5F7586AAE42DB4F9776A977BCBC952CA61E33609DA2B2C329418
* 3) result of extending (SHA1(0x01|0x01|0x00) | 00s to SHA256 size)
* - Value to extend to initial PCR0:
* 47ec8d98366433dc002e7721c9e37d5067547937000000000000000000000000
* - Resulting PCR0:
* 2A7580E5DA289546F4D2E0509CC6DE155EA131818954D36D49E027FD42B8C8F8
* - Policy digest for PolicyCommandCode + PolicyPCR:
* E6EF4F0296AC3EF0F53906480985B1BE8058E0E517E5F74A5B8A415EFE339D87
* Values #2 and #3 correspond to two forms of recovery mode as extended by
* vb2api_get_pcr_digest().
* As a result, the digest allows deleting the space with UndefineSpaceSpecial
* at early RO stages (before extending PCR0) or from recovery mode.
*/
static const uint8_t pcr0_allowed_policy[] = {
0x44, 0x44, 0x79, 0x00, 0xcb, 0xb8, 0x3f, 0x5b, 0x15, 0x76, 0x56,
0x50, 0xef, 0x96, 0x98, 0x0a, 0x2b, 0x96, 0x6e, 0xa9, 0x09, 0x04,
0x4a, 0x01, 0xb8, 0x5f, 0xa5, 0x4a, 0x96, 0xfc, 0x59, 0x84};
static int safe_write(enum cros_nvdata_type type, const void *data, u32 length)
{
int ret;
/* the nvdata_tpm driver handles retrying if needed */
ret = cros_nvdata_write_walk(type, data, length);
if (ret)
return log_msg_ret("fwrite", -EIO);
return 0;
}
static int setup_space(const char *name, enum cros_nvdata_type type,
const void *data, u32 length, const uint nv_attributes,
const uint8_t *nv_policy, size_t nv_policy_size)
{
int ret;
/* the nvdata_tpm driver handles retrying if needed */
ret = cros_nvdata_setup_walk(type, nv_attributes, length, nv_policy,
nv_policy_size);
if (ret)
return log_msg_ret("setup", ret);
log_buffer(UCLASS_TPM, LOGL_INFO, 0, data, 1, length, 0);
ret = safe_write(type, data, length);
if (ret)
return log_msg_ret("write", ret);
return 0;
}
static u32 setup_firmware_space(struct vb2_context *ctx)
{
uint firmware_space_size = vb2api_secdata_firmware_create(ctx);
return setup_space("firmware", CROS_NV_SECDATAF, ctx->secdata_firmware,
firmware_space_size, ro_space_attributes,
pcr0_allowed_policy, sizeof(pcr0_allowed_policy));
}
static uint32_t setup_fwmp_space(struct vb2_context *ctx)
{
uint32_t fwmp_space_size = vb2api_secdata_fwmp_create(ctx);
return setup_space("FWMP", CROS_NV_FWMP, ctx->secdata_fwmp,
fwmp_space_size, fwmp_attr, NULL, 0);
}
static u32 setup_kernel_space(struct vb2_context *ctx)
{
uint kernel_space_size = vb2api_secdata_kernel_create(ctx);
return setup_space("kernel", CROS_NV_SECDATAK, ctx->secdata_kernel,
kernel_space_size, rw_space_attributes, NULL,
0);
}
static u32 set_mrc_hash_space(enum cros_nvdata_type type, const uint8_t *data)
{
if (type == CROS_NV_MRC_REC_HASH) {
return setup_space("RO MRC Hash", type, data, HASH_NV_SIZE,
ro_space_attributes, pcr0_allowed_policy,
sizeof(pcr0_allowed_policy));
} else {
return setup_space("RW MRC Hash", type, data, HASH_NV_SIZE,
rw_space_attributes, NULL, 0);
}
}
static int v2_factory_initialize_tpm(struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
int ret;
log_notice("Init TPM v2\n");
ret = tpm_force_clear(vboot->tpm);
if (ret != TPM_SUCCESS)
return log_msg_ret("clear", -EIO);
/*
* Of all NVRAM spaces defined by this function the firmware space
* must be defined last, because its existence is considered an
* indication that TPM factory initialization was successfully
* completed.
*/
ret = setup_kernel_space(ctx);
if (ret)
return log_msg_ret("kern", ret);
/*
* Define and set rec hash space, if available. No need to
* create the RW hash space because we will definitely boot
* once in normal mode before shipping, meaning that the space
* will get created with correct permissions while still in
* our hands.
*/
if (vboot->has_rec_mode_mrc) {
ret = set_mrc_hash_space(CROS_NV_MRC_REC_HASH, mrc_hash_data);
if (ret)
return log_msg_ret("rec", ret);
}
/* Define and write firmware management parameters space. */
ret = setup_fwmp_space(ctx);
if (ret)
return log_msg_ret("fwmp", ret);
ret = setup_firmware_space(ctx);
if (ret)
return log_msg_ret("fw", ret);
log_warning("done\n");
return TPM_SUCCESS;
}
int antirollback_lock_space_firmware(void)
{
int ret;
ret = cros_nvdata_lock_walk(CROS_NV_SECDATAF);
if (ret)
return log_msg_ret("lock", ret);
return 0;
}
int antirollback_read_space_mrc_hash(enum cros_nvdata_type type, uint8_t *data,
u32 size)
{
if (size != HASH_NV_SIZE) {
log_debug("TPM: Incorrect buffer size for hash type 0x%x. "
"(Expected=0x%x Actual=0x%x).\n", type, HASH_NV_SIZE,
size);
return TPM_E_READ_FAILURE;
}
return read_space_mrc_hash(type, data);
}
int antirollback_write_space_mrc_hash(enum cros_nvdata_type type,
const uint8_t *data, u32 size)
{
uint8_t spc_data[HASH_NV_SIZE];
int ret;
if (size != HASH_NV_SIZE) {
log_debug("TPM: Incorrect buffer size for hash type 0x%x. "
"(Expected=0x%x Actual=0x%x).\n", type, HASH_NV_SIZE,
size);
return TPM_E_WRITE_FAILURE;
}
ret = read_space_mrc_hash(type, spc_data);
if (ret == -ENOENT) {
/*
* If space is not defined already for hash, define
* new space.
*/
log_debug("TPM: Initializing hash space.\n");
return set_mrc_hash_space(type, data);
}
if (ret != TPM_SUCCESS)
return ret;
return safe_write(type, data, size);
}
int antirollback_lock_space_mrc_hash(enum cros_nvdata_type type)
{
int ret;
ret = cros_nvdata_lock_walk(CROS_NV_SECDATAF);
if (ret != TPM_SUCCESS)
return log_msg_ret("flags", ret);
return 0;
}
static int v1_factory_initialize_tpm(struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
struct tpm_permanent_flags pflags;
int ret;
log_notice("Init TPM v1.2\n");
vb2api_secdata_kernel_create_v0(ctx);
ret = tpm1_get_permanent_flags(vboot->tpm, &pflags);
if (ret != TPM_SUCCESS)
return log_msg_ret("flags", -EIO);
/*
* TPM may come from the factory without physical presence finalized.
* Fix if necessary.
*/
log_debug("TPM: physical_presence_lifetime_lock=%d\n",
pflags.physical_presence_lifetime_lock);
if (!pflags.physical_presence_lifetime_lock) {
log_info("TPM: Finalizing physical presence\n");
ret = tpm_finalise_physical_presence(vboot->tpm);
if (ret != TPM_SUCCESS)
return log_msg_ret("final", -EIO);
}
/*
* The TPM will not enforce the NV authorization restrictions until the
* execution of a TPM_NV_DefineSpace with the handle of
* TPM_NV_INDEX_LOCK. Here we create that space if it doesn't already
* exist */
log_debug("TPM: nv_locked=%d\n", pflags.nv_locked);
if (!pflags.nv_locked) {
log_debug("TPM: Enabling NV locking\n");
ret = tpm_nv_enable_locking(vboot->tpm);
if (ret != TPM_SUCCESS)
return log_msg_ret("lock", -EIO);
}
/* Clear TPM owner, in case the TPM is already owned for some reason */
log_debug("TPM: Clearing owner\n");
ret = tpm_clear_and_reenable(vboot->tpm);
if (ret)
return log_msg_ret("enable", -EIO);
/* Define and write secdata_kernel space */
ret = cros_nvdata_setup_walk(CROS_NV_SECDATAK, TPM_NV_PER_PPWRITE,
VB2_SECDATA_KERNEL_SIZE_V02, NULL, 0);
if (ret)
return log_msg_ret("ksetup", -EIO);
ret = cros_nvdata_write_walk(CROS_NV_SECDATAK, ctx->secdata_kernel,
VB2_SECDATA_KERNEL_SIZE_V02);
if (ret)
return log_msg_ret("kwrite", -EIO);
/* Define and write secdata_firmware space */
ret = cros_nvdata_setup_walk(CROS_NV_SECDATAF, TPM_NV_PER_GLOBALLOCK |
TPM_NV_PER_PPWRITE,
VB2_SECDATA_FIRMWARE_SIZE, NULL, 0);
if (ret)
return log_msg_ret("fsetup", -EIO);
ret = cros_nvdata_write_walk(CROS_NV_SECDATAF, ctx->secdata_firmware,
VB2_SECDATA_FIRMWARE_SIZE);
if (ret)
return log_msg_ret("fwrite", -EIO);
log_warning("done\n");
return TPM_SUCCESS;
}
/**
* 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 int factory_initialize_tpm(struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
int ret;
/*
* Set initial values of secdata_firmware space.
* kernel space is created in _factory_initialize_tpm().
*/
vb2api_secdata_firmware_create(ctx);
log_debug("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.
*/
ret = tpm_self_test_full(vboot->tpm);
if (ret)
return log_msg_ret("selftest", ret);
ret = -ENOSYS;
if (tpm_is_v1(vboot->tpm))
ret = v1_factory_initialize_tpm(vboot);
else if (tpm_is_v2(vboot->tpm))
ret = v2_factory_initialize_tpm(vboot);
if (ret)
return log_msg_ret("init", ret);
/*
* _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);
log_debug("TPM: factory initialization successful\n");
return TPM_SUCCESS;
}
int antirollback_read_space_firmware(struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
int ret;
/* Read the firmware space */
ret = read_space_firmware(ctx);
if (ret == -ENOENT) {
/* This seems the first time we've run. Initialize the TPM */
log_warning("TPM: Not initialized yet\n");
ret = factory_initialize_tpm(vboot);
if (ret) {
log_err("TPM: Firmware space in a bad state; giving up\n");
return ret;
}
} else if (ret) {
return log_msg_ret("read", ret);
}
return 0;
}
int antirollback_write_space_firmware(const struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
int ret;
if (vboot->cr50_commit_secdata) {
ret = tpm2_cr50_enable_nvcommits(vboot->tpm);
if (ret)
log_warning("Failed to enable Cr50 NV commits\n");
}
ret = cros_nvdata_write_walk(CROS_NV_SECDATAF, ctx->secdata_firmware,
VB2_SECDATA_FIRMWARE_SIZE);
if (ret)
return log_msg_ret("write", ret);
return 0;
}
int antirollback_write_space_kernel(const struct vboot_info *vboot)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
uint8_t size = VB2_SECDATA_KERNEL_MIN_SIZE;
int ret;
/* Learn the expected 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 important to actually commit changes.
*/
if (vboot->cr50_commit_secdata) {
ret = tpm2_cr50_enable_nvcommits(vboot->tpm);
if (ret)
log_warning("Failed to enable Cr50 NV commits\n");
}
ret = cros_nvdata_write_walk(CROS_NV_SECDATAK, ctx->secdata_kernel,
size);
if (ret)
return log_msg_ret("write", ret);
return 0;
}