Skip to content

Commit 2dea400

Browse files
committed
x86 fsp: check cached training data against a TPM NV hash in stage 1
1 parent 6bcb972 commit 2dea400

9 files changed

Lines changed: 651 additions & 6 deletions

File tree

‎config/examples/nai_68int6.config‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ UCODE0_BIN=src/x86/ucode0.bin
4545
FSP_NVS_CACHE=1
4646
FSP_NVS_BASE=0xff9e0000
4747
FSP_NVS_SIZE=0x20000
48+
# Set to 1 to check the saved training data against a hash in TPM NV before
49+
# FSP-M uses it (needs ANTI_ROLLBACK=1)
50+
FSP_NVS_TPM=0
4851

4952
FSP_T_BIN=./src/x86/fsp_t.bin
5053
FSP_M_BIN=./src/x86/fsp_m.bin

‎docs/Targets.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9111,6 +9111,7 @@ Notable options in [config/examples/nai_68int6.config](../config/examples/nai_68
91119111
| `X86_UART_NUMBER=2` | LPSS UART2 for all three FSP phases (Kontron uses 0) |
91129112
| `ACPI=1` | Required. Generates ACPI tables (RSDP/XSDT/FADT/MADT/MCFG/TPM2/HPET/DMAR and a minimal DSDT with the TPM device); the board's PCI interrupt routing is only in the DSDT `_PRT`, since the MP table is the Kontron one. The DSDT also declares the board devices on LPSS I2C0 (EEPROMs, RTC/watchdog) and GSPI0 (the PRC, for NAI's `naigspi` driver) |
91139113
| `FSP_NVS_CACHE=1`, `FSP_NVS_BASE`, `FSP_NVS_SIZE` | Keeps FSP-M's memory training data (its non-volatile storage HOB) in a 128 KB area of the BIOS region below the wolfBoot window, so later boots skip full memory training. Stage 1 passes a valid copy back through `NvsBufferPtr`; stage 2 rewrites it, before FSP-S and before the BIOS region is locked, only when it changes. The record is keyed on a CRC of the FSP-M image and its UPDs, so a change to either falls back to full training. Stage 1 sets a CMOS flag before FspMemInit runs with a saved record, an FSP-requested reset clears it, and stage 2 clears it once FspSiliconInit succeeds. If FspMemInit fails with the record, or a boot finds the flag still set because the previous boot crashed or hung with the record, boots train from scratch until one gets through FspSiliconInit, and stage 2 replaces the record. The flag is kept in CMOS, so it survives a reset but needs the RTC battery to survive loss of power. `assemble_image.sh -n` blanks the area, so an image built from a board's flash dump does not carry that board's training data |
9114+
| `FSP_NVS_TPM=1` | Authenticates the saved training data, which is otherwise the only unauthenticated input FSP-M consumes. Stage 2 keeps a SHA-256 of the record in a TPM NV index that only the platform hierarchy can write, and stage 1 reads it, before memory is up, and compares it with the record in flash before handing the record to FspMemInit; without a match it trains from scratch. Needs `ANTI_ROLLBACK=1`, which disables the platform hierarchy before handoff so the OS cannot rewrite the hash. The first boot after enabling it trains once. Without this option, prefer `FSP_NVS_CACHE=0` on a Boot Guard image |
91149115
| `FSP_HYPERTHREADING=1` | Hyper-Threading on (off by default). The MADT and the MP table list the enabled logical processors from the CPUID topology, so either setting works |
91159116
| `MP_CPU_NUM_ENTRY=8` | MP table processor entries, one per logical processor (default 4) |
91169117
| `WOLFBOOT_CMDLINE_MAX` | Buffer for a signed `HDR_CMDLINE`, including the terminating NUL (default 256); a longer command line is ignored and the built-in one is used |

‎include/x86/fsp_nvs.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ void fsp_nvs_reject(void);
4646
void fsp_nvs_before_reset(void);
4747
void fsp_nvs_boot_ok(void);
4848
void fsp_nvs_save(void);
49+
void fsp_nvs_tpm_sync(void);
4950

5051
#endif /* FSP_NVS_H */

‎options.mk‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,14 @@ ifeq ($(FSP), 1)
21032103
endif
21042104
CFLAGS+=-DWOLFBOOT_FSP_NVS_CACHE
21052105
CFLAGS+=-DFSP_NVS_BASE=$(FSP_NVS_BASE) -DFSP_NVS_SIZE=$(FSP_NVS_SIZE)
2106+
ifeq ($(FSP_NVS_TPM),1)
2107+
# anti-rollback disables the platform hierarchy before handoff, which
2108+
# is what keeps the OS from rewriting the reference hash
2109+
ifneq ($(ANTI_ROLLBACK),1)
2110+
$(error FSP_NVS_TPM=1 needs ANTI_ROLLBACK=1)
2111+
endif
2112+
CFLAGS+=-DWOLFBOOT_FSP_NVS_TPM
2113+
endif
21062114
endif
21072115
endif
21082116

‎src/loader.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
*/
3131

3232
#include "loader.h"
33+
#ifdef WOLFBOOT_FSP_NVS_TPM
34+
#include "x86/fsp_nvs.h"
35+
#endif
3336
#include "image.h"
3437
#include "hal.h"
3538
#include "hooks.h"
@@ -167,6 +170,9 @@ int main(void)
167170
wolfBoot_panic();
168171
}
169172
#endif
173+
#ifdef WOLFBOOT_FSP_NVS_TPM
174+
fsp_nvs_tpm_sync();
175+
#endif
170176
#ifdef WOLFCRYPT_SECURE_MODE
171177
wcs_Init();
172178
#endif

‎src/x86/fsp_nvs.c‎

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,195 @@ void fsp_nvs_boot_ok(void)
101101
cmos_write(FSP_NVS_STATE_CMOS_IDX, 0);
102102
}
103103

104+
#ifdef WOLFBOOT_FSP_NVS_TPM
105+
#include <wolfssl/wolfcrypt/sha256.h>
106+
107+
/* TPM NV index holding the SHA-256 of the record stage 2 last saved. It is
108+
* created by the platform hierarchy, which wolfBoot disables before handoff,
109+
* so only wolfBoot can write it. */
110+
#ifndef FSP_NVS_TPM_NV_INDEX
111+
#define FSP_NVS_TPM_NV_INDEX 0x01400410U
112+
#endif
113+
#define FSP_NVS_HASH_SIZE 32
114+
115+
/* Only an index the platform hierarchy created, and that it alone can
116+
* write, holds a hash wolfBoot stored. TPMA_NV bits: PPWRITE, OWNERWRITE,
117+
* AUTHWRITE, POLICYWRITE and PLATFORMCREATE. */
118+
#define FSP_NVS_NV_ATTR_MASK 0x4000000FU
119+
#define FSP_NVS_NV_ATTR 0x40000001U
120+
121+
static int fsp_nvs_record_hash(const struct fsp_nvs_record *rec, uint8_t *hash)
122+
{
123+
wc_Sha256 sha;
124+
int ret;
125+
126+
ret = wc_InitSha256(&sha);
127+
if (ret != 0)
128+
return ret;
129+
ret = wc_Sha256Update(&sha, (const uint8_t *)rec, sizeof(*rec) + rec->len);
130+
if (ret == 0)
131+
ret = wc_Sha256Final(&sha, hash);
132+
wc_Sha256Free(&sha);
133+
return ret;
134+
}
135+
#endif /* WOLFBOOT_FSP_NVS_TPM */
136+
104137
#ifdef BUILD_LOADER_STAGE1
138+
#ifdef WOLFBOOT_FSP_NVS_TPM
139+
/* Stage 1 has no writable data before memory is up, so wolfTPM cannot run
140+
* yet. Read the reference hash with a minimal FIFO exchange that keeps all
141+
* of its state on the stack. */
142+
#ifndef FSP_NVS_TPM_BASE
143+
#define FSP_NVS_TPM_BASE 0xFED40000U
144+
#endif
145+
#define TIS_ACCESS (FSP_NVS_TPM_BASE + 0x00)
146+
#define TIS_STS (FSP_NVS_TPM_BASE + 0x18)
147+
#define TIS_FIFO (FSP_NVS_TPM_BASE + 0x24)
148+
#define TIS_ACCESS_VALID 0x80
149+
#define TIS_ACCESS_ACTIVE 0x20
150+
#define TIS_ACCESS_REQUEST 0x02
151+
#define TIS_STS_VALID 0x80
152+
#define TIS_STS_READY 0x40
153+
#define TIS_STS_GO 0x20
154+
#define TIS_STS_AVAIL 0x10
155+
#define TIS_POLL_MAX 2000000U
156+
157+
static int tis_wait_sts(uint8_t mask)
158+
{
159+
uint32_t i;
160+
161+
for (i = 0; i < TIS_POLL_MAX; i++) {
162+
if ((mmio_read8(TIS_STS) & mask) == mask)
163+
return 0;
164+
}
165+
return -1;
166+
}
167+
168+
static int tis_xfer(const uint8_t *cmd, uint32_t cmd_sz, uint8_t *rsp,
169+
uint32_t rsp_max, uint32_t *rsp_sz)
170+
{
171+
uint32_t i, n, total;
172+
int ret = -1;
173+
174+
mmio_write8(TIS_ACCESS, TIS_ACCESS_REQUEST);
175+
for (i = 0; i < TIS_POLL_MAX; i++) {
176+
if ((mmio_read8(TIS_ACCESS) & (TIS_ACCESS_VALID | TIS_ACCESS_ACTIVE)) ==
177+
(TIS_ACCESS_VALID | TIS_ACCESS_ACTIVE))
178+
break;
179+
}
180+
if (i == TIS_POLL_MAX)
181+
return -1;
182+
mmio_write8(TIS_STS, TIS_STS_READY);
183+
if (tis_wait_sts(TIS_STS_READY) != 0)
184+
goto out;
185+
for (i = 0; i < cmd_sz; i++) {
186+
/* burst count, STS bits 8-23 */
187+
for (n = 0; n < TIS_POLL_MAX; n++) {
188+
if ((mmio_read32(TIS_STS) & 0x00FFFF00U) != 0)
189+
break;
190+
}
191+
if (n == TIS_POLL_MAX)
192+
goto out;
193+
mmio_write8(TIS_FIFO, cmd[i]);
194+
}
195+
mmio_write8(TIS_STS, TIS_STS_GO);
196+
total = 10;
197+
for (i = 0; i < total; i++) {
198+
if (tis_wait_sts(TIS_STS_VALID | TIS_STS_AVAIL) != 0 || i >= rsp_max)
199+
goto out;
200+
rsp[i] = mmio_read8(TIS_FIFO);
201+
if (i == 5) {
202+
total = ((uint32_t)rsp[2] << 24) | ((uint32_t)rsp[3] << 16) |
203+
((uint32_t)rsp[4] << 8) | rsp[5];
204+
if (total < 10 || total > rsp_max)
205+
goto out;
206+
}
207+
}
208+
*rsp_sz = total;
209+
ret = 0;
210+
out:
211+
mmio_write8(TIS_STS, TIS_STS_READY);
212+
mmio_write8(TIS_ACCESS, TIS_ACCESS_ACTIVE);
213+
return ret;
214+
}
215+
216+
static uint32_t tpm_rsp_code(const uint8_t *rsp)
217+
{
218+
return ((uint32_t)rsp[6] << 24) | ((uint32_t)rsp[7] << 16) |
219+
((uint32_t)rsp[8] << 8) | rsp[9];
220+
}
221+
222+
/* Returns 0 with the reference hash, or -1 when there is none to trust. */
223+
static int fsp_nvs_tpm_ref(uint8_t *hash)
224+
{
225+
static const uint8_t startup[] = {
226+
0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00
227+
};
228+
uint8_t nv_read[35] = {
229+
0x80, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x01, 0x4e,
230+
0, 0, 0, 0, /* authHandle: the index */
231+
0, 0, 0, 0, /* nvIndex */
232+
0x00, 0x00, 0x00, 0x09, /* authorization size */
233+
0x40, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, /* empty PW */
234+
0x00, FSP_NVS_HASH_SIZE, 0x00, 0x00 /* size, offset */
235+
};
236+
uint8_t nv_public[14] = {
237+
0x80, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x69,
238+
0, 0, 0, 0 /* nvIndex */
239+
};
240+
uint8_t rsp[96];
241+
uint32_t rsp_sz = 0, rc, attr, i;
242+
243+
for (i = 0; i < 4; i++) {
244+
nv_read[10 + i] = nv_read[14 + i] = nv_public[10 + i] =
245+
(uint8_t)(FSP_NVS_TPM_NV_INDEX >> (24 - 8 * i));
246+
}
247+
/* TPM_RC_INITIALIZE (0x100): already started */
248+
if (tis_xfer(startup, sizeof(startup), rsp, sizeof(rsp), &rsp_sz) != 0)
249+
return -1;
250+
rc = tpm_rsp_code(rsp);
251+
if (rc != 0 && rc != 0x100)
252+
return -1;
253+
/* TPMS_NV_PUBLIC: index, name algorithm, then the attributes */
254+
if (tis_xfer(nv_public, sizeof(nv_public), rsp, sizeof(rsp),
255+
&rsp_sz) != 0 || tpm_rsp_code(rsp) != 0 || rsp_sz < 22)
256+
return -1;
257+
attr = ((uint32_t)rsp[18] << 24) | ((uint32_t)rsp[19] << 16) |
258+
((uint32_t)rsp[20] << 8) | rsp[21];
259+
if ((attr & FSP_NVS_NV_ATTR_MASK) != FSP_NVS_NV_ATTR)
260+
return -1;
261+
if (tis_xfer(nv_read, sizeof(nv_read), rsp, sizeof(rsp), &rsp_sz) != 0)
262+
return -1;
263+
if (tpm_rsp_code(rsp) != 0 || rsp_sz < 16 + FSP_NVS_HASH_SIZE ||
264+
rsp[14] != 0 || rsp[15] != FSP_NVS_HASH_SIZE)
265+
return -1;
266+
memcpy(hash, rsp + 16, FSP_NVS_HASH_SIZE);
267+
return 0;
268+
}
269+
270+
static int fsp_nvs_tpm_check(const struct fsp_nvs_record *rec)
271+
{
272+
uint8_t ref[FSP_NVS_HASH_SIZE], hash[FSP_NVS_HASH_SIZE];
273+
uint8_t diff = 0;
274+
int i;
275+
276+
if (fsp_nvs_tpm_ref(ref) != 0) {
277+
wolfBoot_printf("FSP NVS: no TPM reference hash\r\n");
278+
return -1;
279+
}
280+
if (fsp_nvs_record_hash(rec, hash) != 0)
281+
return -1;
282+
for (i = 0; i < FSP_NVS_HASH_SIZE; i++)
283+
diff |= ref[i] ^ hash[i];
284+
if (diff != 0) {
285+
wolfBoot_printf("FSP NVS: saved training data does not match the "
286+
"TPM reference hash\r\n");
287+
return -1;
288+
}
289+
return 0;
290+
}
291+
#endif /* WOLFBOOT_FSP_NVS_TPM */
292+
105293
void fsp_nvs_reject(void)
106294
{
107295
cmos_write(FSP_NVS_STATE_CMOS_IDX, FSP_NVS_STATE_SKIP);
@@ -140,6 +328,10 @@ int fsp_nvs_apply(uint8_t *udp_m, uint32_t udp_size, const uint8_t *fsp_m,
140328
wolfBoot_printf("FSP NVS: no saved training data\r\n");
141329
return 0;
142330
}
331+
#ifdef WOLFBOOT_FSP_NVS_TPM
332+
if (fsp_nvs_tpm_check(rec) != 0)
333+
return 0;
334+
#endif
143335
cmos_write(FSP_NVS_STATE_CMOS_IDX, FSP_NVS_STATE_IN_USE);
144336
arch->NvsBufferPtr = (uint32_t)(uintptr_t)rec->data;
145337
arch->BootMode = BOOT_ASSUMING_NO_CONFIGURATION_CHANGES;
@@ -224,4 +416,77 @@ void fsp_nvs_save(void)
224416
}
225417
wolfBoot_printf("FSP NVS: saved %d bytes of training data\r\n", (int)len);
226418
}
419+
420+
#ifdef WOLFBOOT_FSP_NVS_TPM
421+
#include "tpm.h"
422+
#include <wolftpm/tpm2_wrap.h>
423+
424+
/* Keep the TPM reference hash equal to the record in flash, but only when
425+
* that record holds the data FspMemInit returned on this boot: if the save
426+
* failed, flash may still hold an older record, one stage 1 perhaps rejected.
427+
* Must run before the platform hierarchy is disabled. */
428+
void fsp_nvs_tpm_sync(void)
429+
{
430+
const struct fsp_nvs_record *rec =
431+
(const struct fsp_nvs_record *)(uintptr_t)FSP_NVS_BASE;
432+
WOLFTPM2_DEV *dev = &wolftpm_dev;
433+
WOLFTPM2_HANDLE parent;
434+
WOLFTPM2_NV nv;
435+
NV_Write_In in;
436+
struct stage2_parameter *params = stage2_get_parameters();
437+
struct efi_hob_guid_type *hob;
438+
uint8_t hash[FSP_NVS_HASH_SIZE], ref[FSP_NVS_HASH_SIZE];
439+
word32 attr = 0, sz = sizeof(ref);
440+
int rc;
441+
442+
hob = fsp_nvs_find_hob((struct efi_hob *)(uintptr_t)params->hobList);
443+
if (hob == NULL ||
444+
!fsp_nvs_is_current(rec, params->fsp_nvs_key,
445+
(const uint8_t *)(hob + 1),
446+
hob->header.hob_length - sizeof(*hob)) ||
447+
fsp_nvs_record_hash(rec, hash) != 0)
448+
return;
449+
450+
memset(&nv, 0, sizeof(nv));
451+
nv.handle.hndl = FSP_NVS_TPM_NV_INDEX;
452+
if (wolfTPM2_NVOpen(dev, &nv, FSP_NVS_TPM_NV_INDEX, NULL, 0) == 0) {
453+
if ((nv.attributes & FSP_NVS_NV_ATTR_MASK) != FSP_NVS_NV_ATTR) {
454+
wolfBoot_printf("FSP NVS: TPM reference index is not "
455+
"platform-owned\r\n");
456+
return;
457+
}
458+
wolfTPM2_SetAuthHandle(dev, 0, &nv.handle);
459+
rc = wolfTPM2_NVReadAuth(dev, &nv, FSP_NVS_TPM_NV_INDEX, ref, &sz, 0);
460+
wolfTPM2_UnsetAuth(dev, 0);
461+
if (rc == 0 && sz == sizeof(ref) &&
462+
memcmp(ref, hash, sizeof(ref)) == 0)
463+
return;
464+
}
465+
466+
memset(&parent, 0, sizeof(parent));
467+
parent.hndl = TPM_RH_PLATFORM;
468+
wolfTPM2_GetNvAttributesTemplate(TPM_RH_PLATFORM, &attr);
469+
attr &= ~TPMA_NV_AUTHWRITE;
470+
rc = wolfTPM2_NVCreateAuth(dev, &parent, &nv, FSP_NVS_TPM_NV_INDEX, attr,
471+
sizeof(hash), NULL, 0);
472+
if (rc == TPM_RC_NV_DEFINED)
473+
rc = 0;
474+
wolfTPM2_UnsetAuth(dev, 1);
475+
if (rc == 0) {
476+
memset(&in, 0, sizeof(in));
477+
in.authHandle = TPM_RH_PLATFORM;
478+
in.nvIndex = FSP_NVS_TPM_NV_INDEX;
479+
in.data.size = sizeof(hash);
480+
memcpy(in.data.buffer, hash, sizeof(hash));
481+
wolfTPM2_SetAuthPassword(dev, 0, NULL);
482+
rc = TPM2_NV_Write(&in);
483+
wolfTPM2_UnsetAuth(dev, 0);
484+
}
485+
if (rc != 0)
486+
wolfBoot_printf("FSP NVS: TPM reference hash update failed 0x%x\r\n",
487+
rc);
488+
else
489+
wolfBoot_printf("FSP NVS: TPM reference hash updated\r\n");
490+
}
491+
#endif /* WOLFBOOT_FSP_NVS_TPM */
227492
#endif /* BUILD_LOADER_STAGE1 */

‎tools/unit-tests/Makefile‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,17 +2325,24 @@ unit-boot-x86-fsp-measure: ../../include/target.h unit-boot-x86-fsp-measure.c \
23252325
# unit-fsp-nvs runs the FSP-M memory training data cache record check and
23262326
# the stage 1 hand-off (src/x86/fsp_nvs.c) against a RAM-backed area.
23272327
unit-fsp-nvs:CFLAGS+=-DWOLFBOOT_FSP -DWOLFBOOT_FSP_NVS_CACHE \
2328-
-DBUILD_LOADER_STAGE1 -DPRINTF_ENABLED
2328+
-DWOLFBOOT_FSP_NVS_TPM -DBUILD_LOADER_STAGE1 -DPRINTF_ENABLED
23292329
unit-fsp-nvs: unit-fsp-nvs.c ../../src/x86/fsp_nvs.c
2330-
gcc -o $@ unit-fsp-nvs.c $(CFLAGS) $(LDFLAGS)
2330+
gcc -o $@ unit-fsp-nvs.c \
2331+
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) \
2332+
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
23312333

23322334
# unit-fsp-nvs-save runs the stage 2 save path against a synthetic HOB list
23332335
# and a RAM-backed flash area; built non-PIE so the HOB list sits below 4 GB
23342336
# (stage2_parameter.hobList is 32-bit).
23352337
unit-fsp-nvs-save:CFLAGS+=-DWOLFBOOT_FSP -DWOLFBOOT_FSP_NVS_CACHE \
2336-
-DPRINTF_ENABLED -fno-pie
2338+
-DWOLFBOOT_FSP_NVS_TPM -DWOLFBOOT_TPM -DWOLFTPM_USER_SETTINGS \
2339+
-I$(WOLFBOOT_LIB_WOLFTPM) -D__WOLFBOOT -DWOLFBOOT_HASH_SHA384 \
2340+
-DWOLFBOOT_SIGN_ECC384 -DPRINTF_ENABLED -fno-pie
23372341
unit-fsp-nvs-save: unit-fsp-nvs-save.c ../../src/x86/fsp_nvs.c ../../src/x86/hob.c
2338-
gcc -o $@ unit-fsp-nvs-save.c $(CFLAGS) -no-pie $(LDFLAGS)
2342+
gcc -o $@ unit-fsp-nvs-save.c \
2343+
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) \
2344+
-ffunction-sections -fdata-sections -no-pie $(LDFLAGS) \
2345+
-Wl,--gc-sections
23392346

23402347
# unit-nrf5340-flash-protect runs the real hal_flash_protect() from
23412348
# hal/nrf5340.c against a mock SPU region-permission array (F-13623: the

0 commit comments

Comments
 (0)