/* SPDX-License-Identifier: BSD-3-Clause */ /* * Functions for querying, manipulating and locking rollback indices * stored in the TPM NVRAM. */ #include #include #include #include #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; }