| /* 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; |
| } |