summaryrefslogtreecommitdiff
path: root/src/security/vboot/secdata_tpm.c
blob: 8714dd8fed54b180ff658a65894f887f03f20e67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* 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;
}