diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index f790c8df..206b7fae 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -99,6 +99,10 @@ jobs: # STMicro ST33KTPM2 - name: st33ktpm2 firmware wolftpm_config: --enable-st33 --enable-firmware --disable-fwtpm + # HAL nRST reset control (compile-only, no GPIO hardware in CI) + - name: hal-reset + wolftpm_config: --enable-st33 --enable-hal-reset --disable-fwtpm + test_command: "true" # SPDM + Nuvoton (compile-only, no hardware in CI) - name: spdm-nuvoton wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp diff --git a/.gitignore b/.gitignore index 494a8ace..0e67d092 100644 --- a/.gitignore +++ b/.gitignore @@ -211,8 +211,11 @@ examples/firmware/*.MANIFESTHASH # Firmware TPM files src/fwtpm/fwtpm_server fwtpm_nv.bin +fwtpm_nv.bin.key fwtpm_test_nv.bin fwtpm_test_nv.bin.key +# Catch any NV integrity-key sidecar the fwTPM auto-generates +*.bin.key # Fuzz artifacts (corpus generated at runtime by gen_corpus.py) tests/fuzz/corpus/ diff --git a/README.md b/README.md index b155c7e0..c86cab5e 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,10 @@ TLS_BENCH_MODE Enables TLS benchmarking mode. NO_TPM_BENCH Disables the TPM benchmarking example. WOLFTPM_MAX_RETRIES Default number of times a command is transparently resubmitted when the TPM returns TPM_RC_RETRY (momentarily busy, e.g. persisting the daUsed flag on first auth use of an externally provisioned non-noDA AIK/SUDI key). Disabled by default (0); opt in with TPM2_SetCommandRetries() at runtime or -DWOLFTPM_MAX_RETRIES=N at build time. wolfTPM's own key templates set noDA and never trigger it. WOLFTPM_NO_RETRY Compiles out the TPM_RC_RETRY auto-resubmit handling entirely; TPM_RC_RETRY is returned to the caller for manual handling. +WOLFTPM_LOCALITY_DEFAULT Default TIS locality requested at startup (default 0). Runtime override via wolfTPM2_SetLocality() on SPI/memory-mapped and swtpm transports. The I2C HAL addresses only locality 0 (the TIS locality lives in address bits 12+, which the 8-bit I2C register address cannot carry), so a non-zero wolfTPM2_SetLocality() on I2C returns NOT_COMPILED_IN rather than silently operating at locality 0. +WOLFTPM_TIS_RESET_STALE_LOCALITY At startup, release any other active locality so the default can be granted - recovers a wedge left when a prior session did not return to locality 0. Off by default; single-master buses only, since on a shared bus it could clear a locality another master holds (or use the nRST reset HAL to recover). +WOLFTPM_LOCALITY_TIMEOUT_TRIES Poll attempts when requesting a locality at runtime (default 1000). Kept small so a locality that cannot be granted fails fast. +WOLFTPM_RESET_LINE nRST GPIO line number for the optional reset HAL; set via --enable-hal-reset=LINE and driven with TPM2_IoCb_Reset() (see hal/README.md). ``` Note: For the I2C support on Raspberry Pi you may need to enable I2C. Here are the steps: @@ -401,6 +405,8 @@ make Note: The `--enable-firmware` option enables firmware upgrade support for ST33 TPMs. This adds the `st33_fw_update` example tool for performing firmware updates. +Raspberry Pi wiring: ST33KTPM2X SPI is on `/dev/spidev0.0` with `nRST` (active low) on GPIO24 (pin 18); Nuvoton uses GPIO4. Optionally drive nRST from code with `--enable-hal-reset` and `TPM2_IoCb_Reset()` (see `hal/README.md`). + ### Building Microchip ATTPM20 Build wolfTPM: diff --git a/configure.ac b/configure.ac index a0acae88..7d5f94a1 100644 --- a/configure.ac +++ b/configure.ac @@ -474,6 +474,34 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_ST33" fi +# Optional TPM hardware reset (nRST) HAL control via Linux GPIO char device. +# Accepts an optional GPIO line number (default ST33 GPIO24 / Pi pin 18, Nuvoton GPIO4). +AC_ARG_ENABLE([hal-reset], + [AS_HELP_STRING([--enable-hal-reset@<:@=LINE@:>@],[Enable TPM nRST reset HAL (optional GPIO line; default ST33 GPIO24, Nuvoton GPIO4) (default: disabled)])], + [ ENABLED_HAL_RESET=$enableval ], + [ ENABLED_HAL_RESET=no ] + ) +if test "x$ENABLED_HAL_RESET" != "xno" +then + # The reset HAL implementation (hal/tpm_io.c) only builds for the SPI/I2C + # example HAL, not the native kernel (/dev/tpm0) or swtpm transports, so + # reject those combinations rather than report "yes" with no callable API. + # Note swtpm is the default on Linux x86_64/aarch64, so the hardware reset + # HAL requires explicitly selecting a hardware interface (--enable-spi/-i2c). + if test "x$ENABLED_DEVTPM" = "xyes" || test "x$ENABLED_SWTPM" = "xyes" || test "x$ENABLED_SWTPM" = "xuart" + then + AC_MSG_ERROR([--enable-hal-reset needs the SPI/I2C hardware HAL; it is not available with the swtpm simulator (default on Linux x86_64/aarch64) or --enable-devtpm. Select a hardware interface, e.g. --enable-spi or --enable-i2c.]) + fi + AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_HAL_RESET" + case "x$ENABLED_HAL_RESET" in + xyes) ;; + x|x*[[!0-9]]*) + AC_MSG_ERROR([--enable-hal-reset=LINE requires a numeric GPIO line number (e.g. --enable-hal-reset=24).]) ;; + *) AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_RESET_LINE=$ENABLED_HAL_RESET" ;; + esac + ENABLED_HAL_RESET=yes +fi + # Microchip ATTPM20 AC_ARG_ENABLE([mchp],, [ ENABLED_MCHP=$enableval ], @@ -1132,6 +1160,7 @@ if test "x$ENABLED_SWTPM" = "xyes"; then fi echo " * WINAPI: $ENABLED_WINAPI" echo " * TIS/SPI Check Wait State: $ENABLED_CHECKWAITSTATE" +echo " * HAL Reset (nRST GPIO): $ENABLED_HAL_RESET" echo " * Infineon SLB967X $ENABLED_INFINEON" echo " * STM ST33: $ENABLED_ST" diff --git a/examples/pcr/README.md b/examples/pcr/README.md index 7413d40a..8213a349 100644 --- a/examples/pcr/README.md +++ b/examples/pcr/README.md @@ -36,6 +36,8 @@ Not all PCRs are equal. The user can perform `extend` operation on all PCRs, but * PCR16 is a PCR for debug purposes. This is the PCR used by all tools above by default. It is safe to test and work with PCR16. * PCR17-22 are reserved for Dynamic Root of Trust Measurement (DRTM), an advanced topic that is to be covered separately. +Reset locality (TCG PC Client): PCR16/23 reset at localities 0-3, PCR20-22 at locality 2-4, and PCR17-19 at locality 4. Use `-loc=n` to select the locality (see `wolfTPM2_SetLocality`); it applies to the built-in TIS/SPI driver and the fwTPM. + ### Extend The TPM 2.0 `TPM2_Extend` API uses a SHA1 or SHA256 cryptographic operation to combine the current value of the PCR and with newly provided hash digest. @@ -50,10 +52,13 @@ The TPM 2.0 `TPM2_Quote` API is a standard operation that encapsulates the PCR d ```sh $ ./examples/pcr/reset -? -PCR index is out of range (0-23) +Incorrect arguments Expected usage: -./examples/pcr/reset [pcr] +./examples/pcr/reset [pcr] [-loc=n] * pcr is a PCR index between 0-23 (default 16) +* -loc=n switch to TPM locality n (0-4) before reset + (PCR 17-19 need locality 4; 20-22 need locality 2-4; + enforced by the fwTPM and by discrete TPMs like the ST33) Demo usage without parameters, resets PCR16. ``` diff --git a/examples/pcr/reset.c b/examples/pcr/reset.c index 6c907d1f..06d10cca 100644 --- a/examples/pcr/reset.c +++ b/examples/pcr/reset.c @@ -43,14 +43,19 @@ static void usage(void) { printf("Expected usage:\n"); - printf("./examples/pcr/reset [pcr]\n"); + printf("./examples/pcr/reset [pcr] [-loc=n]\n"); printf("* pcr is a PCR index between 0-23 (default %d)\n", TPM2_TEST_PCR); + printf("* -loc=n switch to TPM locality n (0-4) before reset\n"); + printf(" (PCR 17-19 need locality 4; 20-22 need locality 2-4;\n"); + printf(" enforced by the fwTPM and by discrete TPMs like the ST33)\n"); printf("Demo usage without parameters, resets PCR%d.\n", TPM2_TEST_PCR); } int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]) { int i, j, pcrIndex = TPM2_TEST_PCR, rc = -1; + int locality = -1; /* -1 means leave at default locality */ + int localitySwitched = 0; WOLFTPM2_DEV dev; union { @@ -63,22 +68,32 @@ int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]) byte maxOutput[MAX_RESPONSE_SIZE]; } cmdOut; - if (argc == 2) { - pcrIndex = XATOI(argv[1]); - if (pcrIndex < 0 || pcrIndex > 23 || *argv[1] < '0' || *argv[1] > '9') { - printf("PCR index is out of range (0-23)\n"); + /* Parse arguments: an optional PCR index and an optional -loc=n flag */ + for (i = 1; i < argc; i++) { + if (XSTRNCMP(argv[i], "-loc=", 5) == 0) { + /* Require exactly one digit 0-4; XATOI would silently map + * non-numeric input (e.g. -loc=abc or -loc=) to locality 0. */ + if (argv[i][5] < '0' || argv[i][5] > '4' || argv[i][6] != '\0') { + printf("Locality is out of range (0-4)\n"); + usage(); + goto exit_badargs; + } + locality = argv[i][5] - '0'; + } + else if (argv[i][0] >= '0' && argv[i][0] <= '9') { + pcrIndex = XATOI(argv[i]); + if (pcrIndex < 0 || pcrIndex > 23) { + printf("PCR index is out of range (0-23)\n"); + usage(); + goto exit_badargs; + } + } + else { + printf("Incorrect arguments\n"); usage(); goto exit_badargs; } } - else if (argc == 1) { - pcrIndex = TPM2_TEST_PCR; - } - else { - printf("Incorrect arguments\n"); - usage(); - goto exit_badargs; - } printf("Demo how to reset a PCR (clear PCR value)\n"); rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); @@ -88,6 +103,18 @@ int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]) } printf("wolfTPM2_Init: success\n"); + if (locality >= 0) { + printf("Switching to locality %d...\n", locality); + rc = wolfTPM2_SetLocality(&dev, locality); + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_SetLocality(%d) failed 0x%x: %s\n", locality, rc, + TPM2_GetRCString(rc)); + goto exit; + } + printf("wolfTPM2_SetLocality: now at locality %d\n", locality); + localitySwitched = 1; + } + /* Prepare PCR Reset command */ XMEMSET(&cmdIn.pcrReset, 0, sizeof(cmdIn.pcrReset)); cmdIn.pcrReset.pcrHandle = pcrIndex; @@ -116,6 +143,13 @@ int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]) exit: + /* Return to the default locality so a non-zero one is not left active + * (which would block the next TPM startup on TPMs that do not preempt). + * Only when a switch actually succeeded - never on a failed Init. */ + if (localitySwitched) { + (void)wolfTPM2_SetLocality(&dev, 0); + } + wolfTPM2_Cleanup(&dev); exit_badargs: diff --git a/examples/run_examples.sh b/examples/run_examples.sh index e0b549fc..840063c7 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -1184,6 +1184,22 @@ echo -e "Endorsement Key (EK) and Certificate" RESULT=$? [ $RESULT -ne 0 ] && echo -e "get_ek_certs failed! $RESULT" && exit 1 +# PCR reset locality (-loc): smoke-exercise wolfTPM2_SetLocality. Reset PCR 16 +# at locality 0 (universally resettable, a no-op switch). This runs against +# whichever backend is present (ibmswtpm2, fwTPM, ...) so it does not assert the +# backend-specific per-PCR map - that rigor lives in tests/fwtpm_check.sh. +echo -e "PCR reset locality (-loc)" +LOC_OUT=$(./examples/pcr/reset 16 -loc=0 2>&1) +echo "$LOC_OUT" >> $TPMPWD/run.out +if echo "$LOC_OUT" | grep -q "TPM2_PCR_Reset success"; then + : # -loc supported and works +elif echo "$LOC_OUT" | grep -qiE "not compiled in|NOT_COMPILED_IN"; then + # kernel (/dev/tpm0) and Windows TBS own the locality; TPM2_GetRCString + # renders NOT_COMPILED_IN as "Feature not compiled in" under wolfCrypt. + echo -e " -loc not supported by this backend, skipping" +else + echo -e "pcr reset -loc failed!" && exit 1 +fi rm -f keyblob.bin diff --git a/examples/spdm/README.md b/examples/spdm/README.md index 9c939c3e..9580a096 100644 --- a/examples/spdm/README.md +++ b/examples/spdm/README.md @@ -98,15 +98,17 @@ effect. The reset pin must be connected and controllable by the host. to a host-controllable GPIO. Without reset pin control, SPDM mode changes cannot be applied and recovery from SPDM-only mode is not possible. -### Raspberry Pi Example (GPIO 4) +The reset line is board specific. On a Raspberry Pi, Nuvoton uses GPIO4 and the +ST33KTPM uses GPIO24 (pin 18); confirm your wiring before toggling. ```bash -# Assert reset low, wait, release high, wait for TPM startup +# Assert reset low, release high, wait for TPM startup (Nuvoton GPIO4 shown) gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +# ST33: use line 24 instead of 4 ``` -Other platforms will use their own GPIO control mechanism. The key requirement -is toggling the TPM reset line (active low) with sufficient hold time. +wolfTPM can also drive this from code: build with `--enable-hal-reset` and call +`TPM2_IoCb_Reset()` (default line: ST33 GPIO24, Nuvoton GPIO4). See `hal/README.md`. ## Automated Test Suite diff --git a/hal/README.md b/hal/README.md index 5ada136b..ef790822 100644 --- a/hal/README.md +++ b/hal/README.md @@ -59,6 +59,13 @@ int TPM2_IoCb(TPM2_CTX* ctx, const byte* txBuf, byte* rxBuf, * `WOLFTPM_CHECK_WAIT_STATE`: Enables check of the wait state during a SPI transaction. Most TPM 2.0 chips require this and typically only require 0-2 wait cycles depending on the command. Only the Infineon TPM's guarantee no wait states. * `WOLFTPM_ADV_IO`: Enables advanced IO callback mode that includes TIS register and read/write flag. This is requires for I2C, but can be used with SPI also. * `WOLFTPM_DEBUG_IO`: Enable logging of the IO (if using the example HAL). +* `WOLFTPM_HAL_RESET`: Optional TPM hardware reset (nRST) control in the example HAL (`--enable-hal-reset`). On Linux, `TPM2_IoCb_Reset(&dev->ctx, userCtx)` pulses nRST (active low) via the GPIO char device (raw GPIO v2 uAPI, no libgpiod). + +## TPM reset (nRST) HAL macros (when `WOLFTPM_HAL_RESET` is set) + +* `WOLFTPM_RESET_GPIOCHIP`: GPIO char device. Default: `/dev/gpiochip0` +* `WOLFTPM_RESET_LINE`: GPIO line wired to nRST. Default: ST33 = `24` (GPIO24, Pi pin 18), Nuvoton = `4` (GPIO4). Also settable via `--enable-hal-reset=`. +* `WOLFTPM_RESET_HOLD_US` / `WOLFTPM_RESET_SETTLE_US`: reset hold / post-reset settle time (us). Defaults: `300000` / `1000000`. ## Additional Compiler macros diff --git a/hal/tpm_io.c b/hal/tpm_io.c index 9c1c6ef4..d5757c4d 100644 --- a/hal/tpm_io.c +++ b/hal/tpm_io.c @@ -239,6 +239,29 @@ int TPM2_IoCb(TPM2_CTX* ctx, const BYTE* txBuf, BYTE* rxBuf, } #endif /* WOLFTPM_ADV_IO */ + +#ifdef WOLFTPM_HAL_RESET +/* Pulse the TPM hardware reset (nRST) line to reset the TPM. Dispatches to the + * platform implementation. Returns TPM_RC_SUCCESS on success. */ +int TPM2_IoCb_Reset(TPM2_CTX* ctx, void* userCtx) +{ + int ret; +#if defined(__linux__) + ret = TPM2_IoCb_Linux_Reset(ctx, userCtx); +#else + /* No reset HAL for this OS - return NOT_COMPILED_IN so callers can tell it + * apart from a genuine reset failure (TPM_RC_FAILURE) on a supported OS. */ + (void)ctx; + (void)userCtx; + ret = NOT_COMPILED_IN; + #ifdef DEBUG_WOLFTPM + printf("TPM reset HAL not implemented for this platform\n"); + #endif +#endif + return ret; +} +#endif /* WOLFTPM_HAL_RESET */ + #endif /* !(WOLFTPM_LINUX_DEV || WOLFTPM_SWTPM || WOLFTPM_WINAPI) */ /******************************************************************************/ diff --git a/hal/tpm_io.h b/hal/tpm_io.h index ecd681b9..83783819 100644 --- a/hal/tpm_io.h +++ b/hal/tpm_io.h @@ -145,6 +145,15 @@ WOLFTPM_LOCAL int TPM2_IoCb_FwTPM(TPM2_CTX* ctx, int isRead, word32 addr, byte* buf, word16 size, void* userCtx); #endif +#ifdef WOLFTPM_HAL_RESET +/* Optional TPM hardware reset (nRST) control. Pulses the reset line to reset + * the TPM. Enable with --enable-hal-reset (-DWOLFTPM_HAL_RESET). */ +WOLFTPM_API int TPM2_IoCb_Reset(TPM2_CTX* ctx, void* userCtx); +#if defined(__linux__) +WOLFTPM_LOCAL int TPM2_IoCb_Linux_Reset(TPM2_CTX* ctx, void* userCtx); +#endif +#endif + #endif /* WOLFTPM_EXAMPLE_HAL */ #endif /* !(WOLFTPM_LINUX_DEV || WOLFTPM_SWTPM || WOLFTPM_WINAPI) */ diff --git a/hal/tpm_io_linux.c b/hal/tpm_io_linux.c index dcc4fdf1..6f969577 100644 --- a/hal/tpm_io_linux.c +++ b/hal/tpm_io_linux.c @@ -64,6 +64,11 @@ #include #include #include + #ifdef WOLFTPM_HAL_RESET + /* GPIO character-device uAPI for optional nRST control */ + #include + #include /* nanosleep (usleep is undefined for >= 1s) */ + #endif #ifdef WOLFTPM_I2C /* I2C - (Only tested with SLB9673 and ST33 I2C) */ @@ -415,6 +420,110 @@ return ret; } #endif /* WOLFTPM_I2C */ + +#ifdef WOLFTPM_HAL_RESET + /* Pulse the TPM nRST (active low) via the Linux GPIO char device (raw GPIO + * v2 uAPI, no libgpiod). Default line: Raspberry Pi ST33 = GPIO24 (pin 18), + * Nuvoton = GPIO4; override with WOLFTPM_RESET_GPIOCHIP / WOLFTPM_RESET_LINE. */ + #ifndef WOLFTPM_RESET_GPIOCHIP + #define WOLFTPM_RESET_GPIOCHIP "/dev/gpiochip0" + #endif + #ifndef WOLFTPM_RESET_LINE + #if defined(WOLFTPM_NUVOTON) + #define WOLFTPM_RESET_LINE 4 + #else + #define WOLFTPM_RESET_LINE 24 + #endif + #endif + #ifndef WOLFTPM_RESET_HOLD_US + #define WOLFTPM_RESET_HOLD_US 300000 /* reset asserted 300ms */ + #endif + #ifndef WOLFTPM_RESET_SETTLE_US + #define WOLFTPM_RESET_SETTLE_US 1000000 /* TPM boot settle 1s */ + #endif + + /* usleep() is undefined for values >= 1000000 (POSIX); nanosleep has no + * such limit and handles the 1s settle and any larger override. */ + static void TPM2_Reset_DelayUs(unsigned long us) + { + struct timespec ts; + ts.tv_sec = (time_t)(us / 1000000UL); + ts.tv_nsec = (long)((us % 1000000UL) * 1000UL); + (void)nanosleep(&ts, NULL); + } + + /* Note: this reset HAL is only compile-checked in CI (no GPIO hardware or + * gpio-sim there); the open/GET_LINE/SET_VALUES flow, the hold/settle + * timing, and the fd lifecycle are functionally regression-verified on real + * hardware - the ST33 on a Raspberry Pi 5, nRST wired to GPIO24 (pin 18). */ + int TPM2_IoCb_Linux_Reset(TPM2_CTX* ctx, void* userCtx) + { + int ret = TPM_RC_FAILURE; + int chipFd, reqFd; + struct gpio_v2_line_request req; + struct gpio_v2_line_values vals; + + (void)ctx; + (void)userCtx; + + chipFd = open(WOLFTPM_RESET_GPIOCHIP, O_RDONLY); + if (chipFd < 0) { + #ifdef DEBUG_WOLFTPM + printf("TPM Reset: open %s failed (errno %d)\n", + WOLFTPM_RESET_GPIOCHIP, errno); + #endif + return TPM_RC_FAILURE; + } + + /* Acquire the line as an output driven low (assert reset) */ + XMEMSET(&req, 0, sizeof(req)); + req.offsets[0] = (unsigned int)WOLFTPM_RESET_LINE; + req.num_lines = 1; + req.config.flags = GPIO_V2_LINE_FLAG_OUTPUT; + req.config.num_attrs = 1; + req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES; + req.config.attrs[0].attr.values = 0; /* drive low (assert reset) */ + req.config.attrs[0].mask = 1; /* applies to line index 0 */ + XMEMCPY(req.consumer, "wolfTPM-reset", sizeof("wolfTPM-reset")); + + if (ioctl(chipFd, GPIO_V2_GET_LINE_IOCTL, &req) < 0 || req.fd < 0) { + #ifdef DEBUG_WOLFTPM + printf("TPM Reset: GET_LINE ioctl failed (errno %d)\n", errno); + #endif + close(chipFd); + return TPM_RC_FAILURE; + } + close(chipFd); + reqFd = req.fd; + + /* Hold reset asserted, then release (drive high) and let the TPM boot */ + TPM2_Reset_DelayUs(WOLFTPM_RESET_HOLD_US); + + XMEMSET(&vals, 0, sizeof(vals)); + vals.mask = 1; + vals.bits = 1; /* drive high = release reset */ + if (ioctl(reqFd, GPIO_V2_LINE_SET_VALUES_IOCTL, &vals) < 0) { + #ifdef DEBUG_WOLFTPM + printf("TPM Reset: SET_VALUES ioctl failed (errno %d)\n", errno); + #endif + } + else { + ret = TPM_RC_SUCCESS; + #ifdef DEBUG_WOLFTPM + printf("TPM Reset: pulsed nRST on %s line %d\n", + WOLFTPM_RESET_GPIOCHIP, (int)WOLFTPM_RESET_LINE); + #endif + /* Only wait for the TPM to settle after a successful release; on a + * failed release the delay would just stall the error path. */ + TPM2_Reset_DelayUs(WOLFTPM_RESET_SETTLE_US); + } + + close(reqFd); + + return ret; + } +#endif /* WOLFTPM_HAL_RESET */ + #endif /* __linux__ */ #endif /* !(WOLFTPM_LINUX_DEV || WOLFTPM_SWTPM || WOLFTPM_WINAPI) */ #endif /* WOLFTPM_INCLUDE_IO_FILE */ diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ebf9690b..dfb07ede 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1076,6 +1076,30 @@ static TPM_RC FwCmd_StirRandom(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, return rc; } +/* Per-PCR reset/extend locality helper (defined with its tables below). */ +static int FwPcrLocalityAllowed(int pcrIndex, int locality, int isReset); + +/* Mask a TPM command code to its 16-bit value (strip vendor/reserved bits). */ +#define FW_CC_MASK 0x0000FFFFu +/* PCRs 0-15 are the SRTM set that TPM_PT_PCR_SAVE reports as saved. */ +#define FWTPM_PCR_SAVE_COUNT 16 +/* pcrProps[].kind: how a TPM_CAP_PCR_PROPERTIES row maps to the locality table. */ +#define FW_PCR_KIND_EXTEND 0 /* extend allowed at loc */ +#define FW_PCR_KIND_RESET 1 /* reset allowed at loc */ +#define FW_PCR_KIND_SAVE 2 /* saved across power cycles (PCR 0-15) */ +#define FW_PCR_KIND_DRTM_RESET 3 /* reset allowed at locality 4 */ + +/* Overwrite a big-endian UINT32 already appended at buf[pos]. Used to + * back-patch a TPML count with the number of entries actually emitted, so a + * count/payload mismatch is impossible even if two entries collapse to one. */ +static void FwPatchU32BE(byte* buf, int pos, UINT32 v) +{ + buf[pos + 0] = (byte)(v >> 24); + buf[pos + 1] = (byte)(v >> 16); + buf[pos + 2] = (byte)(v >> 8); + buf[pos + 3] = (byte)(v); +} + /* --- TPM2_GetCapability (CC 0x017A) --- */ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -1086,6 +1110,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 propertyCount = 0; UINT32 i; int paramSzPos, paramStart; + int moreDataPos; (void)ctx; @@ -1108,7 +1133,9 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); - /* moreData (TPMI_YES_NO) */ + /* moreData (TPMI_YES_NO) - default NO; a capability case that truncates its + * list to propertyCount back-patches this byte to YES via moreDataPos. */ + moreDataPos = rsp->pos; TPM2_Packet_AppendU8(rsp, 0); /* NO */ /* capability */ @@ -1165,14 +1192,51 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, { TPM_ALG_NULL, 0x0000 }, }; int numAlgs = (int)(sizeof(algList) / sizeof(algList[0])); - if (propertyCount < (UINT32)numAlgs) - numAlgs = (int)propertyCount; + int avail = 0; + int numOut; + int k, best; + int emitted = 0, countPos; + UINT32 lastAlg = 0; + int haveLast = 0; + + /* Honor the property cursor: count entries at/after the requested + * start algorithm, then cap to propertyCount. moreData=YES only + * when entries still remain past this page (spec-correct paging; + * emitting from index 0 would loop a paging client forever). */ + for (k = 0; k < numAlgs; k++) { + if ((UINT32)algList[k].alg >= property) + avail++; + } + numOut = avail; + if ((UINT32)numOut > propertyCount) + numOut = (int)propertyCount; + if (avail > numOut) + rsp->buf[moreDataPos] = 1; /* YES - more entries remain */ - TPM2_Packet_AppendU32(rsp, (UINT32)numAlgs); - for (i = 0; i < (UINT32)numAlgs; i++) { - TPM2_Packet_AppendU16(rsp, algList[i].alg); - TPM2_Packet_AppendU32(rsp, algList[i].attrs); - } + /* Reserve the count and back-patch it to entries actually emitted. */ + countPos = rsp->pos; + TPM2_Packet_AppendU32(rsp, (UINT32)numOut); + /* Emit in ascending algorithm-ID order (the static list is not + * sorted) via repeated min-selection; n is small. */ + for (i = 0; i < (UINT32)numOut; i++) { + best = -1; + for (k = 0; k < numAlgs; k++) { + if ((UINT32)algList[k].alg < property) + continue; + if (haveLast && (UINT32)algList[k].alg <= lastAlg) + continue; + if (best < 0 || algList[k].alg < algList[best].alg) + best = k; + } + if (best < 0) + break; /* defensive: no further candidates */ + TPM2_Packet_AppendU16(rsp, algList[best].alg); + TPM2_Packet_AppendU32(rsp, algList[best].attrs); + lastAlg = algList[best].alg; + haveLast = 1; + emitted++; + } + FwPatchU32BE(rsp->buf, countPos, (UINT32)emitted); break; } @@ -1180,14 +1244,51 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Iterate the single source-of-truth dispatch table instead of * a parallel hand-maintained list. */ int numCmds = FwGetCmdCount(); - if (propertyCount < (UINT32)numCmds) - numCmds = (int)propertyCount; + int avail = 0; + int numOut; + int k, best; + int emitted = 0, countPos; + UINT32 cc, bestCc = 0, lastCc = 0; + int haveLast = 0; + + /* Honor the property cursor and page in ascending command-code + * order (the dispatch table is not sorted); see TPM_CAP_ALGS + * above for the rationale. */ + for (k = 0; k < numCmds; k++) { + cc = (UINT32)FwGetCmdCcAt(k) & FW_CC_MASK; + if (cc >= property) + avail++; + } + numOut = avail; + if ((UINT32)numOut > propertyCount) + numOut = (int)propertyCount; + if (avail > numOut) + rsp->buf[moreDataPos] = 1; /* YES - more entries remain */ - TPM2_Packet_AppendU32(rsp, (UINT32)numCmds); - for (i = 0; i < (UINT32)numCmds; i++) { - TPM2_Packet_AppendU32(rsp, - (UINT32)FwGetCmdCcAt((int)i) & 0x0000FFFF); - } + /* Reserve the count and back-patch it to entries actually emitted. */ + countPos = rsp->pos; + TPM2_Packet_AppendU32(rsp, (UINT32)numOut); + for (i = 0; i < (UINT32)numOut; i++) { + best = -1; + for (k = 0; k < numCmds; k++) { + cc = (UINT32)FwGetCmdCcAt(k) & FW_CC_MASK; + if (cc < property) + continue; + if (haveLast && cc <= lastCc) + continue; + if (best < 0 || cc < bestCc) { + best = k; + bestCc = cc; + } + } + if (best < 0) + break; /* defensive: no further candidates */ + TPM2_Packet_AppendU32(rsp, bestCc); + lastCc = bestCc; + haveLast = 1; + emitted++; + } + FwPatchU32BE(rsp->buf, countPos, (UINT32)emitted); break; } @@ -1316,6 +1417,10 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, if ((UINT32)numOut > propertyCount) numOut = (int)propertyCount; + /* Truncated (start offset or propertyCount cap): flag moreData=YES. */ + if (startIdx + numOut < totalProps) + rsp->buf[moreDataPos] = 1; /* YES */ + TPM2_Packet_AppendU32(rsp, (UINT32)numOut); for (i = 0; i < (UINT32)numOut; i++) { UINT32 prop = allProps[startIdx + i].prop; @@ -1355,24 +1460,65 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, } case TPM_CAP_PCR_PROPERTIES: { - int numBanks = FWTPM_PCR_BANKS; - if (propertyCount < (UINT32)numBanks) - numBanks = (int)propertyCount; + /* Emit a TPML_TAGGED_PCR_PROPERTY from the per-PCR locality table so + * this report matches the reset/extend enforcement above (kind = + * FW_PCR_KIND_*). */ + static const struct { UINT32 tag; byte kind; byte loc; } pcrProps[] = { + { TPM_PT_PCR_SAVE, FW_PCR_KIND_SAVE, 0 }, + { TPM_PT_PCR_EXTEND_L0, FW_PCR_KIND_EXTEND, 0 }, + { TPM_PT_PCR_RESET_L0, FW_PCR_KIND_RESET, 0 }, + { TPM_PT_PCR_EXTEND_L1, FW_PCR_KIND_EXTEND, 1 }, + { TPM_PT_PCR_RESET_L1, FW_PCR_KIND_RESET, 1 }, + { TPM_PT_PCR_EXTEND_L2, FW_PCR_KIND_EXTEND, 2 }, + { TPM_PT_PCR_RESET_L2, FW_PCR_KIND_RESET, 2 }, + { TPM_PT_PCR_EXTEND_L3, FW_PCR_KIND_EXTEND, 3 }, + { TPM_PT_PCR_RESET_L3, FW_PCR_KIND_RESET, 3 }, + { TPM_PT_PCR_EXTEND_L4, FW_PCR_KIND_EXTEND, 4 }, + { TPM_PT_PCR_RESET_L4, FW_PCR_KIND_RESET, 4 }, + { TPM_PT_PCR_DRTM_RESET, FW_PCR_KIND_DRTM_RESET, 4 } + }; + int totalProps = (int)(sizeof(pcrProps) / sizeof(pcrProps[0])); + int startIdx = 0; + int numOut, p, set; + UINT32 ii; + UINT32 tag; + byte kind, loc; + byte sel[PCR_SELECT_MAX]; - TPM2_Packet_AppendU32(rsp, (UINT32)numBanks); - if (numBanks > 0) { - TPM2_Packet_AppendU32(rsp, TPM_ALG_SHA256); - TPM2_Packet_AppendU8(rsp, PCR_SELECT_MAX); - TPM2_Packet_AppendU8(rsp, 0xFF); - TPM2_Packet_AppendU8(rsp, 0xFF); - TPM2_Packet_AppendU8(rsp, 0xFF); + for (startIdx = 0; startIdx < totalProps; startIdx++) { + if (pcrProps[startIdx].tag >= property) + break; } - if (numBanks > 1) { - TPM2_Packet_AppendU32(rsp, TPM_ALG_SHA384); + numOut = totalProps - startIdx; + if (numOut < 0) + numOut = 0; + if ((UINT32)numOut > propertyCount) + numOut = (int)propertyCount; + + /* Truncated (start offset or propertyCount cap): flag moreData=YES. */ + if (startIdx + numOut < totalProps) + rsp->buf[moreDataPos] = 1; /* YES */ + + TPM2_Packet_AppendU32(rsp, (UINT32)numOut); + for (ii = 0; ii < (UINT32)numOut; ii++) { + tag = pcrProps[startIdx + ii].tag; + kind = pcrProps[startIdx + ii].kind; + loc = pcrProps[startIdx + ii].loc; + XMEMSET(sel, 0, sizeof(sel)); + for (p = 0; p < IMPLEMENTATION_PCR; p++) { + if (kind == FW_PCR_KIND_SAVE) + set = (p < FWTPM_PCR_SAVE_COUNT); + else if (kind == FW_PCR_KIND_DRTM_RESET) + set = FwPcrLocalityAllowed(p, WOLFTPM_LOCALITY_MAX, 1); + else + set = FwPcrLocalityAllowed(p, (int)loc, (int)kind); + if (set) + sel[p / 8] |= (byte)(1u << (p % 8)); + } + TPM2_Packet_AppendU32(rsp, tag); TPM2_Packet_AppendU8(rsp, PCR_SELECT_MAX); - TPM2_Packet_AppendU8(rsp, 0xFF); - TPM2_Packet_AppendU8(rsp, 0xFF); - TPM2_Packet_AppendU8(rsp, 0xFF); + for (p = 0; p < PCR_SELECT_MAX; p++) + TPM2_Packet_AppendU8(rsp, sel[p]); } break; } @@ -1442,8 +1588,10 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* Other classes (PCR, permanent): report 0 */ - if ((UINT32)count > propertyCount) + if ((UINT32)count > propertyCount) { count = (int)propertyCount; + rsp->buf[moreDataPos] = 1; /* YES - more handles available */ + } TPM2_Packet_AppendU32(rsp, (UINT32)count); if (count > 0) { int emitted = 0; @@ -1777,6 +1925,55 @@ static TPM_RC FwCmd_PCR_Read(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } /* --- TPM2_PCR_Extend (CC 0x0182) --- */ +/* Per-PCR reset/extend locality bitmaps (bit L = locality L is allowed), the + * single source of truth for enforcement and the TPM_CAP_PCR_PROPERTIES report. + * Matches ibmswtpm2 PlatformPCR.c s_initAttributes and the ST33 GetCapability + * dump (loc2 reset of PCR 20-22 hardware-confirmed over SPI). + * reset: 0-15 never; 16,23 loc0-3; 17-19 loc4; 20-22 loc2-4. + * extend: 0-16,23 any; 17,18 loc2-4; 19 loc2-3; 20 loc1-3; 21,22 loc2. */ +static const byte fwPcrResetLocality[IMPLEMENTATION_PCR] = { + /* 0- 7 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 8-15 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 16 */ 0x0F, /* loc0-3 */ + /* 17 */ 0x10, /* loc4 */ + /* 18 */ 0x10, /* loc4 */ + /* 19 */ 0x10, /* loc4 */ + /* 20 */ 0x1C, /* loc2-4 */ + /* 21 */ 0x1C, /* loc2-4 */ + /* 22 */ 0x1C, /* loc2-4 */ + /* 23 */ 0x0F /* loc0-3 */ +}; + +static const byte fwPcrExtendLocality[IMPLEMENTATION_PCR] = { + /* 0- 7 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, /* any */ + /* 8-15 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, /* any */ + /* 16 */ 0x1F, /* any */ + /* 17 */ 0x1C, /* loc2-4 */ + /* 18 */ 0x1C, /* loc2-4 */ + /* 19 */ 0x0C, /* loc2-3 */ + /* 20 */ 0x0E, /* loc1-3 */ + /* 21 */ 0x04, /* loc2 */ + /* 22 */ 0x04, /* loc2 */ + /* 23 */ 0x1F /* any */ +}; + +/* Return 1 if locality may reset (isReset != 0) or extend (isReset == 0) the + * given PCR, else 0; out-of-range PCR or locality is rejected. */ +static int FwPcrLocalityAllowed(int pcrIndex, int locality, int isReset) +{ + byte mask; + + if (pcrIndex < 0 || pcrIndex >= IMPLEMENTATION_PCR) { + return 0; + } + if (locality < 0 || locality > WOLFTPM_LOCALITY_MAX) { + return 0; + } + mask = isReset ? fwPcrResetLocality[pcrIndex] + : fwPcrExtendLocality[pcrIndex]; + return (mask & (byte)(1u << locality)) ? 1 : 0; +} + static TPM_RC FwCmd_PCR_Extend(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { @@ -1801,6 +1998,15 @@ static TPM_RC FwCmd_PCR_Extend(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } } + /* Enforce the per-PCR extend locality (e.g. DRTM PCRs 17-22 cannot be + * extended from locality 0). Same source-of-truth table as PCR_Reset. */ + if (rc == 0) { + pcrIndex = pcrHandle - PCR_FIRST; + if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 0)) { + rc = TPM_RC_LOCALITY; + } + } + /* Skip authorization area if sessions tag */ if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { rc = FwSkipAuthArea(cmd, cmdSize); @@ -1891,28 +2097,13 @@ static TPM_RC FwCmd_PCR_Reset(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } } + /* Enforce reset locality from the source-of-truth table (PCR 0-15 never + * user-resettable, reset only via TPM2_Startup(CLEAR); DRTM PCRs restricted). + * Without this a caller at locality 0 could wipe DRTM PCRs and defeat + * attestation policies sealed to them. */ if (rc == 0) { pcrIndex = pcrHandle - PCR_FIRST; - /* PCR 0-15 (SRTM) are not user-resettable per TPM 2.0 Part 2 - * Table 3-8; they reset only via TPM2_Startup(CLEAR). */ - if (pcrIndex < 16) { - rc = TPM_RC_LOCALITY; - } - } - - /* Per TCG PC Client TPM Profile Table 5, PCR_Reset locality rules - * for indices 16..23 are: - * 16, 23 — any locality - * 17 — locality 4 only (DRTM MLE) - * 18..22 — locality 3 or 4 (DRTM ACM/OS) - * Without this check any caller at locality 0 can wipe DRTM PCRs - * and defeat attestation policies sealed to them. */ - if (rc == 0) { - if (pcrIndex == 17 && ctx->activeLocality != 4) { - rc = TPM_RC_LOCALITY; - } - else if (pcrIndex >= 18 && pcrIndex <= 22 && - ctx->activeLocality != 3 && ctx->activeLocality != 4) { + if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 1)) { rc = TPM_RC_LOCALITY; } } @@ -1968,15 +2159,11 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* DRTM PCRs are locality-restricted (Part 1 Sec.11.4.6): - * PCR 17 requires locality 4; PCRs 18-22 require locality 3 or 4. */ + /* PCR_Event extends the target PCR, so enforce the extend locality from + * the source-of-truth table (DRTM PCRs 17-22 are restricted). */ if (rc == 0) { pcrIndex = pcrHandle - PCR_FIRST; - if (pcrIndex == 17 && ctx->activeLocality != 4) { - rc = TPM_RC_LOCALITY; - } - else if (pcrIndex >= 18 && pcrIndex <= 22 && - ctx->activeLocality != 3 && ctx->activeLocality != 4) { + if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 0)) { rc = TPM_RC_LOCALITY; } } @@ -7885,13 +8072,9 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Extend the result into the PCR */ if (rc == 0 && pcrHandle <= PCR_LAST) { pcrIndex = pcrHandle - PCR_FIRST; - /* DRTM PCRs are locality-restricted (Part 1 Sec.11.4.6): - * PCR 17 requires locality 4; PCRs 18-22 require locality 3 or 4. */ - if (pcrIndex == 17 && ctx->activeLocality != 4) { - rc = TPM_RC_LOCALITY; - } - else if (pcrIndex >= 18 && pcrIndex <= 22 && - ctx->activeLocality != 3 && ctx->activeLocality != 4) { + /* This extends the target PCR, so enforce the extend locality from the + * source-of-truth table (DRTM PCRs 17-22 are restricted). */ + if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 0)) { rc = TPM_RC_LOCALITY; } bank = FwGetPcrBankIndex(seqHashAlg); @@ -16341,7 +16524,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } /* Enforce any PolicyLocality constraint bound to the session */ if (pSess->hasRequiredLocality && - (ctx->activeLocality > 4 || + (ctx->activeLocality > WOLFTPM_LOCALITY_MAX || !((1u << ctx->activeLocality) & pSess->requiredLocality))) { *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index a03f8c33..c7ec2310 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -72,9 +72,16 @@ static UINT32 TisBuildSts(BYTE stsFlags, UINT16 burstCount) static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) { UINT32 offset = TisRegOffset(regs->reg_addr); + /* The client sends the full TIS address; the locality is in bits 12-15. */ + int loc = (int)((regs->reg_addr >> 12) & 0xF); UINT32 len = regs->reg_len; + /* Only the locality that owns the interface may drive the command engine + * (STS/GO/FIFO). ACCESS and read-only ID/INT registers are not gated. */ + int isOwner; FWTPM_DECLARE_BUF(localCmd, FWTPM_TIS_FIFO_SIZE); + isOwner = (ctx->tisLocality >= 0 && loc == ctx->tisLocality); + if (regs->reg_is_write) { /* --- Write operations --- */ UINT32 val = 0; @@ -85,23 +92,46 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) switch (offset) { case FWTPM_TIS_ACCESS: - if (val & FWTPM_ACCESS_REQUEST_USE) { - /* Grant locality 0 */ - regs->access = FWTPM_ACCESS_VALID | - FWTPM_ACCESS_ACTIVE_LOCALITY; - regs->sts = TisBuildSts( - FWTPM_STS_VALID | FWTPM_STS_COMMAND_READY, - FWTPM_TIS_BURST_COUNT); - } - if (val & FWTPM_ACCESS_ACTIVE_LOCALITY) { - /* Release locality */ - regs->access = FWTPM_ACCESS_VALID; - regs->sts = TisBuildSts(0, 0); + /* Only localities 0-4 exist; ignore requests for any other + * (an out-of-range locality must not own the interface). */ + if (loc >= 0 && loc <= 4) { + if (val & FWTPM_ACCESS_REQUEST_USE) { + /* Grant if the interface is free or already owned by it + * (release-before-request: a different owner must + * relinquish first - the host handles that). */ + if (ctx->tisLocality < 0 || ctx->tisLocality == loc) { + /* On a fresh grant (interface was unowned) clear any + * leftover command/response FIFO state so the new + * owner cannot drain the previous locality's bytes + * without first writing COMMAND_READY. */ + if (ctx->tisLocality < 0) { + regs->cmd_len = 0; + regs->fifo_write_pos = 0; + regs->fifo_read_pos = 0; + regs->rsp_len = 0; + } + ctx->tisLocality = loc; + regs->sts = TisBuildSts( + FWTPM_STS_VALID | FWTPM_STS_COMMAND_READY, + FWTPM_TIS_BURST_COUNT); + } + } + if (val & FWTPM_ACCESS_ACTIVE_LOCALITY) { + /* Release only if it is the current owner */ + if (ctx->tisLocality == loc) { + ctx->tisLocality = -1; + regs->sts = TisBuildSts(0, 0); + } + } } break; case FWTPM_TIS_STS: case FWTPM_TIS_STS + 1: /* burst count write (ignored) */ + /* A non-owning locality cannot ready/execute the FIFO */ + if (!isOwner) { + break; + } if (val & FWTPM_STS_COMMAND_READY) { /* Reset FIFO for new command */ regs->cmd_len = 0; @@ -117,24 +147,28 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) * prevent TOCTOU if cmd_buf is in shared memory */ UINT32 localCmdLen; int rspSize = 0; - int procRc; + int procRc = TPM_RC_FAILURE; + TPM_RC rc = TPM_RC_SUCCESS; /* FWTPM_ALLOC_BUF sets on OOM */ FWTPM_ALLOC_BUF(localCmd, FWTPM_TIS_FIFO_SIZE); - - localCmdLen = regs->cmd_len; - if (localCmdLen > FWTPM_TIS_FIFO_SIZE) { - localCmdLen = FWTPM_TIS_FIFO_SIZE; + if (rc == TPM_RC_SUCCESS) { + localCmdLen = regs->cmd_len; + if (localCmdLen > FWTPM_TIS_FIFO_SIZE) { + localCmdLen = FWTPM_TIS_FIFO_SIZE; + } + XMEMCPY(localCmd, regs->cmd_buf, localCmdLen); + + #ifdef DEBUG_WOLFTPM + printf("fwTPM TIS: GO cmd_len=%u\n", localCmdLen); + #endif + + /* Gated to the owner above, so loc == ctx->tisLocality: + * execute under the addressed (owning) locality. */ + procRc = FWTPM_ProcessCommand(ctx, + localCmd, (int)localCmdLen, + regs->rsp_buf, &rspSize, loc); + FWTPM_FREE_BUF(localCmd); } - XMEMCPY(localCmd, regs->cmd_buf, localCmdLen); - - #ifdef DEBUG_WOLFTPM - printf("fwTPM TIS: GO cmd_len=%u\n", localCmdLen); - #endif - - procRc = FWTPM_ProcessCommand(ctx, - localCmd, (int)localCmdLen, - regs->rsp_buf, &rspSize, 0 /* locality */); - FWTPM_FREE_BUF(localCmd); if (procRc != TPM_RC_SUCCESS || rspSize == 0) { /* Build minimal error response * TPM_ST_NO_SESSIONS = 0x8001 (big-endian) */ @@ -174,6 +208,10 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) UINT32 space; /* Snapshot write position to local for TOCTOU safety */ UINT32 wpos = regs->fifo_write_pos; + /* Only the owning locality may load the command FIFO */ + if (!isOwner) { + break; + } if (wpos >= FWTPM_TIS_FIFO_SIZE) { regs->fifo_write_pos = 0; break; @@ -245,18 +283,26 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) switch (offset) { case FWTPM_TIS_ACCESS: - val = regs->access; + /* Report per requested locality: VALID always set, plus + * ACTIVE_LOCALITY only for the current owner. Never 0xFF (the + * host treats 0xFF as an unimplemented/absent locality). */ + val = FWTPM_ACCESS_VALID | + ((ctx->tisLocality == loc) ? + FWTPM_ACCESS_ACTIVE_LOCALITY : 0); break; case FWTPM_TIS_STS: - val = regs->sts; + /* Only the owner sees command/data state; a non-owner gets + * VALID only (no COMMAND_READY/DATA_AVAIL to act on). */ + val = isOwner ? regs->sts : (UINT32)FWTPM_STS_VALID; break; case FWTPM_TIS_BURST_COUNT_REG: /* Burst count is at offset 0x0019, which is bytes [1..2] * of the 4-byte STS register. Return just the burst count - * portion (upper 16 bits shifted down). */ - val = (regs->sts >> 8) & 0xFFFFu; + * portion (upper 16 bits shifted down). A non-owner cannot + * transfer, so it reads a zero burst count. */ + val = isOwner ? ((regs->sts >> 8) & 0xFFFFu) : 0; break; case FWTPM_TIS_DATA_FIFO: @@ -272,6 +318,11 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) * our clamped len), so any stale shared-memory bytes would * leak prior-operation data into the client. */ XMEMSET(regs->reg_data, 0, sizeof(regs->reg_data)); + /* Only the owning locality may drain the response FIFO; + * a non-owner gets the zero-filled buffer (no data). */ + if (!isOwner) { + return; + } if (rpos > rlen || rpos >= sizeof(regs->rsp_buf)) { avail = 0; } @@ -382,8 +433,9 @@ int FWTPM_TIS_Init(FWTPM_CTX* ctx) regs->magic = FWTPM_TIS_MAGIC; regs->version = FWTPM_TIS_VERSION; - /* Power-on register defaults */ - regs->access = FWTPM_ACCESS_VALID; + /* Power-on register defaults. ACCESS is not stored here: it is served + * dynamically per access from ctx->tisLocality (see TisHandleRegAccess), + * so the regs->access field is left zeroed by the XMEMSET above. */ regs->sts = TisBuildSts(0, 0); regs->intf_caps = FWTPM_INTF_BURST_COUNT_STATIC | FWTPM_INTF_DATA_AVAIL_INT | @@ -396,6 +448,9 @@ int FWTPM_TIS_Init(FWTPM_CTX* ctx) /* Auto power-on in TIS mode (no platform port to signal power) */ ctx->powerOn = 1; + /* Power-on: no locality owns the interface yet */ + ctx->tisLocality = -1; + return TPM_RC_SUCCESS; } diff --git a/src/tpm2.c b/src/tpm2.c index 0e41aa05..734a97ec 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -739,7 +739,13 @@ TPM_RC TPM2_ChipStartup(TPM2_CTX* ctx, int timeoutTries) rc = TPM2_TIS_StartupWait(ctx, timeoutTries); if (rc == TPM_RC_SUCCESS) { - /* Request locality for TPM module */ + /* Request locality for TPM module. When built with + * WOLFTPM_TIS_RESET_STALE_LOCALITY this also relinquishes a stale + * non-default locality left active by a prior session (for example + * after wolfTPM2_SetLocality) that would otherwise block acquiring + * the default one on TPMs that do not preempt. That self-heal is + * off by default, so a default build simply requests the default + * locality without relinquishing anything. */ rc = TPM2_TIS_RequestLocality(ctx, timeoutTries); if (rc == TPM_RC_SUCCESS) { diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index f81ea96b..9293e23b 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -512,6 +512,7 @@ int TPM2_SWTPM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) int rc = TPM_RC_SUCCESS; uint32_t rspSz = 0; uint32_t tss_word; + byte localityByte; const char* swtpmHost = TPM2_SWTPM_HOST; const char* swtpmPort = XSTRINGIFY(TPM2_SWTPM_PORT); #ifndef NO_GETENV @@ -548,9 +549,12 @@ int TPM2_SWTPM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) rc = SwTpmTransmit(ctx, &tss_word, sizeof(uint32_t)); } - /* locality */ + /* locality (single byte; copy through a byte so the value - not the + * low byte of the host-endian int ctx->locality - is what goes on the + * wire, otherwise localities 1-4 serialize as 0 on big-endian hosts) */ if (rc == TPM_RC_SUCCESS) { - rc = SwTpmTransmit(ctx, &ctx->locality, sizeof(uint8_t)); + localityByte = (byte)ctx->locality; + rc = SwTpmTransmit(ctx, &localityByte, sizeof(localityByte)); } /* buffer size */ diff --git a/src/tpm2_tis.c b/src/tpm2_tis.c index 3b67c5b8..9253de33 100644 --- a/src/tpm2_tis.c +++ b/src/tpm2_tis.c @@ -265,7 +265,7 @@ int TPM2_TIS_StartupWait(TPM2_CTX* ctx, int timeout) rc = TPM2_TIS_Read(ctx, TPM_ACCESS(0), &access, sizeof(access)); /* if chip isn't present MISO will be high and return 0xFF */ if (rc == TPM_RC_SUCCESS && (access & TPM_ACCESS_VALID) && - (access != 0xFF)) { + (access != TPM_TIS_ACCESS_INVALID)) { return TPM_RC_SUCCESS; } XTPM_WAIT(); @@ -286,7 +286,10 @@ int TPM2_TIS_CheckLocality(TPM2_CTX* ctx, int locality, byte* access) static int TPM2_TIS_CheckLocalityAccessValid(TPM2_CTX* ctx, int locality, byte access) { - if ((access & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + /* Reject a floating/unimplemented locality (see TPM_TIS_ACCESS_INVALID); + * otherwise e.g. an unsupported locality 4 would look active. */ + if (access != TPM_TIS_ACCESS_INVALID && + (access & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) { ctx->locality = locality; return locality; @@ -294,10 +297,9 @@ static int TPM2_TIS_CheckLocalityAccessValid(TPM2_CTX* ctx, int locality, return -1; } -int TPM2_TIS_RequestLocality(TPM2_CTX* ctx, int timeout) +int TPM2_TIS_RequestLocalityEx(TPM2_CTX* ctx, int locality, int timeout) { int rc; - int locality = WOLFTPM_LOCALITY_DEFAULT; byte access = 0; rc = TPM2_TIS_CheckLocality(ctx, locality, &access); @@ -330,6 +332,43 @@ int TPM2_TIS_RequestLocality(TPM2_CTX* ctx, int timeout) return rc; } +int TPM2_TIS_RequestLocality(TPM2_CTX* ctx, int timeout) +{ + int locality = WOLFTPM_LOCALITY_DEFAULT; +#ifdef WOLFTPM_TIS_RESET_STALE_LOCALITY + int l; + byte access = 0; + + /* Recover a wedge (see WOLFTPM_TIS_RESET_STALE_LOCALITY in tpm2_tis.h): if + * the default locality is not active, release any other active one so it + * can be granted instead of spinning to timeout. */ + if (TPM2_TIS_CheckLocality(ctx, locality, &access) == TPM_RC_SUCCESS && + (access & TPM_ACCESS_ACTIVE_LOCALITY) == 0) { + for (l = 0; l <= WOLFTPM_LOCALITY_MAX; l++) { + if (l == locality) { + continue; + } + access = 0; + if (TPM2_TIS_CheckLocality(ctx, l, &access) == TPM_RC_SUCCESS && + access != TPM_TIS_ACCESS_INVALID && + (access & TPM_ACCESS_ACTIVE_LOCALITY)) { + (void)TPM2_TIS_ReleaseLocality(ctx, l); + } + } + } +#endif /* WOLFTPM_TIS_RESET_STALE_LOCALITY */ + + return TPM2_TIS_RequestLocalityEx(ctx, locality, timeout); +} + +int TPM2_TIS_ReleaseLocality(TPM2_CTX* ctx, int locality) +{ + /* Relinquish the locality by writing the active locality bit. The TPM + * clears its active locality so another locality can be requested. */ + byte access = TPM_ACCESS_ACTIVE_LOCALITY; + return TPM2_TIS_Write(ctx, TPM_ACCESS(locality), &access, sizeof(access)); +} + int TPM2_TIS_GetInfo(TPM2_CTX* ctx) { int rc; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index a531514e..9e349dd3 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -24,6 +24,7 @@ #endif #include +#include #include #ifdef WOLFTPM_SPDM #include @@ -6701,6 +6702,93 @@ int wolfTPM2_ResetPCR(WOLFTPM2_DEV* dev, int pcrIndex) return rc; } +int wolfTPM2_SetLocality(WOLFTPM2_DEV* dev, int locality) +{ +#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_WINAPI) + /* The Linux kernel driver and Windows TBS own the TPM locality */ + (void)dev; + (void)locality; + return NOT_COMPILED_IN; +#elif defined(WOLFTPM_SWTPM) + /* The swtpm/mssim transport carries the locality with each command + * (see TPM2_SWTPM_SendCommand), so just record it for subsequent + * commands - no TIS handshake is involved. */ + if (dev == NULL || locality < 0 || locality > 4) { + return BAD_FUNC_ARG; + } + dev->ctx.locality = locality; + return TPM_RC_SUCCESS; +#else + /* Built-in TIS driver (SPI/memory-mapped). The fwtpm-tis CI config exercises + * both the immediate-grant and the release-and-retry branches below: the + * fwTPM SHM-TIS emulation does not preempt (like the ST33), so switching + * locality there forces the release-then-retry path. Only the + * restore-on-failure branch (target ungrantable) is not auto-triggered; it + * was checked on the ST33 over SPI. */ + int rc; + int oldLocality; + + if (dev == NULL || locality < 0 || locality > 4) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFTPM_I2C + /* The I2C HAL transmits only the low 8 address bits, so the TIS locality + * (address bits 12+) never reaches the TPM. A non-zero locality would read + * back locality 0's ACCESS register and falsely appear granted, so reject + * it until locality-aware I2C addressing is implemented. */ + if (locality != 0) { + return NOT_COMPILED_IN; + } +#endif + +#ifdef WOLFTPM_LINUX_DEV_AUTODETECT + /* When the Linux kernel driver is in use the kernel owns the locality */ + if (dev->ctx.fd >= 0) { + return NOT_COMPILED_IN; + } +#endif + + oldLocality = dev->ctx.locality; + if (oldLocality == locality) { + return TPM_RC_SUCCESS; /* already active */ + } + + /* TPM2_TIS_RequestLocalityEx returns the granted locality (0-4) on success, + * or a TPM_RC_* code on failure (which may be positive), so success is + * exactly rc == locality. Try to acquire the target while holding the + * current locality (bounded timeout, so it fails fast when the TPM does not + * preempt). */ + rc = TPM2_TIS_RequestLocalityEx(&dev->ctx, locality, + WOLFTPM_LOCALITY_TIMEOUT_TRIES); + if (rc != locality) { + /* No preemption: relinquish the current locality and retry */ + (void)TPM2_TIS_ReleaseLocality(&dev->ctx, oldLocality); + rc = TPM2_TIS_RequestLocalityEx(&dev->ctx, locality, + WOLFTPM_LOCALITY_TIMEOUT_TRIES); + if (rc != locality) { + /* Not grantable - restore the old locality so the TPM stays usable. + * If that restore also fails, rc still holds the failed (non-success) + * target request, so the caller sees an error and can re-init. + * This branch is reached only when the target locality is + * ungrantable; the fwTPM SHM emulation and CI cannot force that, so + * it is regression-verified only on ST33 hardware (over SPI). */ + (void)TPM2_TIS_RequestLocalityEx(&dev->ctx, oldLocality, + WOLFTPM_LOCALITY_TIMEOUT_TRIES); + } + } + else if (oldLocality < locality) { + /* Preempted the lower locality; relinquish it now */ + (void)TPM2_TIS_ReleaseLocality(&dev->ctx, oldLocality); + } + if (rc == locality) { + rc = TPM_RC_SUCCESS; /* granted; ctx->locality now the target */ + } + + return rc; +#endif +} + /* TODO: Version that can read up to 8 PCR's at a time */ int wolfTPM2_ReadPCR(WOLFTPM2_DEV* dev, int pcrIndex, int hashAlg, byte* digest, int* pDigestLen) diff --git a/tests/fwtpm_check.sh b/tests/fwtpm_check.sh index 40df31c1..dd3f38ec 100755 --- a/tests/fwtpm_check.sh +++ b/tests/fwtpm_check.sh @@ -382,6 +382,36 @@ else SKIP=$((SKIP + 1)) fi +# --- fwTPM PCR reset locality (end-to-end) --- +# Both transports carry the command locality: the mssim/socket transport in its +# SEND_COMMAND framing, and the TIS/SHM transport via the ACCESS-register +# handshake driven by wolfTPM2_SetLocality. So this exercises the fwTPM's +# per-PCR locality enforcement end-to-end in either mode. +# Only run when we started our own fwtpm_server: these asserts encode the fwTPM +# per-PCR map, which differs from an external server (e.g. ibmswtpm2) that CI may +# have left listening on the port (which fwtpm_check.sh reuses). +FWTPM_RESET="$BUILD_DIR/examples/pcr/reset" +if [ "${STARTED_SERVER:-0}" -eq 1 ] && [ -x "$FWTPM_RESET" ]; then + echo "" + echo "=== fwTPM PCR reset locality ===" + cd "$BUILD_DIR" + LOC_OK=1 + # PCR 20 resets at locality 2, PCR 17 at locality 4; both rejected at 0. + TPM2_SWTPM_PORT="$FWTPM_PORT" "$FWTPM_RESET" 20 -loc=2 2>&1 \ + | grep -q "TPM2_PCR_Reset success" || LOC_OK=0 + TPM2_SWTPM_PORT="$FWTPM_PORT" "$FWTPM_RESET" 17 -loc=4 2>&1 \ + | grep -q "TPM2_PCR_Reset success" || LOC_OK=0 + TPM2_SWTPM_PORT="$FWTPM_PORT" "$FWTPM_RESET" 17 -loc=0 2>&1 \ + | grep -q "TPM_RC_LOCALITY" || LOC_OK=0 + if [ $LOC_OK -eq 1 ]; then + PASS=$((PASS + 1)) + echo "PASS: fwTPM PCR reset locality" + else + FAIL=$((FAIL + 1)) + echo "FAIL: fwTPM PCR reset locality" + fi +fi + # --- Run examples --- if [ $SKIP_EXAMPLES -eq 1 ]; then diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a19c9541..f9c361b8 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -568,6 +568,9 @@ static void test_fwtpm_getcap_algorithms(void) { FWTPM_CTX ctx; int rc, rspSize, cmdSz; + byte moreData; + UINT32 cap, count; + UINT16 firstAlg; memset(&ctx, 0, sizeof(ctx)); rc = fwtpm_test_startup(&ctx); @@ -585,6 +588,43 @@ static void test_fwtpm_getcap_algorithms(void) AssertIntEQ(rc, TPM_RC_SUCCESS); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); AssertIntGT(rspSize, TPM2_HEADER_SIZE); + /* Full list fits: moreData must be NO. Response body: + * [0]=moreData, [1..4]=capability, [5..8]=count, [9..]=entries. */ + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 0); + + /* Paging: ask for one algorithm from the start (property=0). moreData + * must be YES and count exactly 1 (regression: emitting from index 0 with + * moreData=YES but ignoring the cursor loops a paging client forever). */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_ALGS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; /* property = 0 */ + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; /* propertyCount = 1 */ + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + moreData = gRsp[TPM2_HEADER_SIZE]; + cap = GetU32BE(gRsp + TPM2_HEADER_SIZE + 1); + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + firstAlg = GetU16BE(gRsp + TPM2_HEADER_SIZE + 9); + AssertIntEQ(moreData, 1); + AssertIntEQ(cap, TPM_CAP_ALGS); + AssertIntEQ(count, 1); + + /* Next page from firstAlg+1 must advance (ascending, no page-1 repeat). */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_ALGS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, (UINT32)firstAlg + 1); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 1); + AssertIntGT(GetU16BE(gRsp + TPM2_HEADER_SIZE + 9), firstAlg); FWTPM_Cleanup(&ctx); fwtpm_pass("GetCapability(ALGS):", 0); @@ -594,6 +634,8 @@ static void test_fwtpm_getcap_commands(void) { FWTPM_CTX ctx; int rc, rspSize, cmdSz; + byte moreData; + UINT32 cap, count, firstCc; memset(&ctx, 0, sizeof(ctx)); rc = fwtpm_test_startup(&ctx); @@ -611,6 +653,41 @@ static void test_fwtpm_getcap_commands(void) AssertIntEQ(rc, TPM_RC_SUCCESS); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); AssertIntGT(rspSize, TPM2_HEADER_SIZE); + /* Full list fits: moreData must be NO. */ + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 0); + + /* Paging: ask for one command from the start. moreData must be YES and + * count exactly 1 (regression guard against cursor-ignoring paging). */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_COMMANDS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; /* property = 0 (from start) */ + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; /* propertyCount = 1 */ + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + moreData = gRsp[TPM2_HEADER_SIZE]; + cap = GetU32BE(gRsp + TPM2_HEADER_SIZE + 1); + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + firstCc = GetU32BE(gRsp + TPM2_HEADER_SIZE + 9); + AssertIntEQ(moreData, 1); + AssertIntEQ(cap, TPM_CAP_COMMANDS); + AssertIntEQ(count, 1); + + /* Next page from firstCc+1 must advance (ascending, no page-1 repeat). */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_COMMANDS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, firstCc + 1); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 1); + AssertIntGT(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9), firstCc); FWTPM_Cleanup(&ctx); fwtpm_pass("GetCapability(COMMANDS):", 0); @@ -666,6 +743,79 @@ static void test_fwtpm_getcap_pcrs(void) fwtpm_pass("GetCapability(PCRS):", 0); } +/* Page a capability list one entry at a time and assert it (a) terminates, + * (b) is strictly ascending, and (c) covers exactly the full-list count. + * idIs16=1 for TPM_CAP_ALGS (U16 alg ids), 0 for TPM_CAP_COMMANDS (U32 cc). */ +static void getcap_paging_check(FWTPM_CTX* ctx, UINT32 cap, int idIs16) +{ + int rc, rspSize, cmdSz, total, iters; + UINT32 property, count, id, prev, fullCount; + byte moreData; + + /* Full list in one shot: must fit, so moreData=NO. */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, cap); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 1024); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 0); + fullCount = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + AssertIntGT((int)fullCount, 1); + + /* Walk the cursor one entry per page. */ + property = 0; prev = 0; total = 0; + for (iters = 0; iters < 1024; iters++) { + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, cap); cmdSz += 4; + PutU32BE(gCmd + cmdSz, property); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + moreData = gRsp[TPM2_HEADER_SIZE]; + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + if (count == 1) { + id = idIs16 ? (UINT32)GetU16BE(gRsp + TPM2_HEADER_SIZE + 9) + : GetU32BE(gRsp + TPM2_HEADER_SIZE + 9); + if (total > 0) + AssertIntGT((int)id, (int)prev); /* strictly ascending */ + prev = id; + property = id + 1; + total++; + } + else { + AssertIntEQ((int)count, 0); /* a 1-entry request yields 0 or 1 */ + } + if (moreData == 0) + break; + AssertIntEQ((int)count, 1); /* moreData=YES must carry the entry */ + } + AssertIntGT(1024, iters); /* terminated (no infinite loop) */ + AssertIntEQ(total, (int)fullCount); /* every entry seen exactly once */ +} + +static void test_fwtpm_getcap_paging(void) +{ + FWTPM_CTX ctx; + int rc; + + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + + getcap_paging_check(&ctx, TPM_CAP_ALGS, 1); + getcap_paging_check(&ctx, TPM_CAP_COMMANDS, 0); + getcap_paging_check(&ctx, TPM_CAP_TPM_PROPERTIES, 0); + getcap_paging_check(&ctx, TPM_CAP_PCR_PROPERTIES, 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("GetCapability paging convergence:", 0); +} + /* ================================================================== */ /* 6. PCR Operations */ /* ================================================================== */ @@ -8235,6 +8385,40 @@ static void test_fwtpm_gettestresult_needs_test_then_success(void) printf("Test fwTPM:\tGetTestResult NEEDS_TEST then SUCCESS:\tPassed\n"); } +/* Send TPM2_PCR_Reset for pcrIndex from the given locality; return the RC. */ +static TPM_RC SendPcrResetLoc(FWTPM_CTX* ctx, int pcrIndex, int locality) +{ + int pos = 0, rspSize = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PCR_Reset); pos += 4; + PutU32BE(gCmd + pos, (UINT32)pcrIndex); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, locality); + return GetRspRC(gRsp); +} + +/* Send TPM2_PCR_Extend (one SHA-256 zero digest) for pcrIndex from the given + * locality; return the RC. */ +static TPM_RC SendPcrExtendLoc(FWTPM_CTX* ctx, int pcrIndex, int locality) +{ + int pos = 0, rspSize = 0; + byte digest[32]; + memset(digest, 0, sizeof(digest)); + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PCR_Extend); pos += 4; + PutU32BE(gCmd + pos, (UINT32)pcrIndex); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, 1); pos += 4; /* digestCount */ + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* hashAlg */ + memcpy(gCmd + pos, digest, 32); pos += 32; /* digest */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, locality); + return GetRspRC(gRsp); +} + static void test_fwtpm_pcr_reset(void) { FWTPM_CTX ctx; @@ -8249,41 +8433,168 @@ static void test_fwtpm_pcr_reset(void) fwtpm_pass("PCR_Reset(16):", 0); } -/* Per TCG PC Client TPM Profile Table 5, PCR 17 (DRTM MLE) may only be - * reset from locality 4. The default test locality is 0, so the reset - * must be rejected with TPM_RC_LOCALITY. Same expectation for PCR 22 - * which requires locality 3 or 4. */ +/* Per the TCG PC Client TPM Profile per-PCR reset locality map: + * PCR 16, 23 reset from localities 0-3 (not 4); + * PCR 17-19 reset only from locality 4; + * PCR 20-22 reset from localities 2-4; + * PCR 0-15 are never user-resettable. + * Verify both the reject (wrong locality) and allow (correct locality) paths. */ static void test_fwtpm_pcr_reset_locality_enforced(void) { FWTPM_CTX ctx; - int pos, rspSize; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); - pos = 0; - PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; - PutU32BE(gCmd + pos, 0); pos += 4; - PutU32BE(gCmd + pos, TPM_CC_PCR_Reset); pos += 4; - PutU32BE(gCmd + pos, 17); pos += 4; - pos = AppendPwAuth(gCmd, pos, NULL, 0); - PutU32BE(gCmd + 2, (UINT32)pos); - rspSize = 0; - FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + /* PCR 16 / 23: allowed at loc 0-3, rejected at loc 4 */ + AssertIntEQ(SendPcrResetLoc(&ctx, 16, 0), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 23, 3), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 16, 4), TPM_RC_LOCALITY); + + /* PCR 0-15: never resettable */ + AssertIntEQ(SendPcrResetLoc(&ctx, 0, 0), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrResetLoc(&ctx, 15, 4), TPM_RC_LOCALITY); + + /* PCR 20-22: rejected at loc 0/1, allowed at loc 2-4 */ + AssertIntEQ(SendPcrResetLoc(&ctx, 20, 0), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrResetLoc(&ctx, 20, 1), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrResetLoc(&ctx, 20, 2), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 21, 3), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 22, 4), TPM_RC_SUCCESS); + + /* PCR 17-19: locality 4 only */ + AssertIntEQ(SendPcrResetLoc(&ctx, 17, 0), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrResetLoc(&ctx, 17, 3), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrResetLoc(&ctx, 17, 4), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 18, 4), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrResetLoc(&ctx, 19, 4), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Reset locality map enforced:", 0); +} + +/* Per-PCR extend locality map: 0-16,23 any; 17,18 loc2-4; 19 loc2-3; + * 20 loc1-3; 21,22 loc2. Verify reject and allow paths. */ +static void test_fwtpm_pcr_extend_locality_enforced(void) +{ + FWTPM_CTX ctx; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Non-DRTM PCRs extend at any locality (incl. 0) */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 0, 0), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 16, 0), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 23, 0), TPM_RC_SUCCESS); + + /* DRTM PCRs rejected at locality 0 */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 17, 0), TPM_RC_LOCALITY); + AssertIntEQ(SendPcrExtendLoc(&ctx, 22, 0), TPM_RC_LOCALITY); + + /* 17,18: loc 2-4 */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 17, 2), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 18, 4), TPM_RC_SUCCESS); + /* 19: loc 2-3, not 4 */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 19, 3), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 19, 4), TPM_RC_LOCALITY); + /* 20: loc 1-3, not 4 */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 20, 1), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 20, 4), TPM_RC_LOCALITY); + /* 21,22: loc 2 only */ + AssertIntEQ(SendPcrExtendLoc(&ctx, 21, 2), TPM_RC_SUCCESS); + AssertIntEQ(SendPcrExtendLoc(&ctx, 22, 3), TPM_RC_LOCALITY); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Extend locality map enforced:", 0); +} + +/* TPM_CAP_PCR_PROPERTIES must report a well-formed TPML_TAGGED_PCR_PROPERTY + * whose RESET_Lx / EXTEND_Lx / DRTM_RESET bitmaps match the enforcement table. */ +static int PcrSelHas(const byte* sel, int selSz, int pcr) +{ + int byteIdx = pcr / 8; + if (byteIdx >= selSz) + return 0; + return (sel[byteIdx] >> (pcr % 8)) & 1; +} + +static void test_fwtpm_pcr_properties_capability(void) +{ + FWTPM_CTX ctx; + int pos, rspSize, p, i; + UINT32 cap, count; + byte resetL0[8], resetL4[8], extendL0[8], drtm[8]; + int gotResetL0 = 0, gotResetL4 = 0, gotExtendL0 = 0, gotDrtm = 0; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(resetL0, 0, sizeof(resetL0)); + memset(resetL4, 0, sizeof(resetL4)); + memset(extendL0, 0, sizeof(extendL0)); + memset(drtm, 0, sizeof(drtm)); pos = 0; - PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; - PutU32BE(gCmd + pos, TPM_CC_PCR_Reset); pos += 4; - PutU32BE(gCmd + pos, 22); pos += 4; - pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, TPM_CC_GetCapability); pos += 4; + PutU32BE(gCmd + pos, TPM_CAP_PCR_PROPERTIES); pos += 4; + PutU32BE(gCmd + pos, TPM_PT_PCR_FIRST); pos += 4; + PutU32BE(gCmd + pos, 32); pos += 4; PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - FWTPM_Cleanup(&ctx); - fwtpm_pass("PCR_Reset locality enforced (LOCALITY):", 0); + /* header(10) + moreData(1) + capability(4) + count(4) + properties */ + p = TPM2_HEADER_SIZE + 1; + cap = GetU32BE(gRsp + p); p += 4; + AssertIntEQ(cap, TPM_CAP_PCR_PROPERTIES); + count = GetU32BE(gRsp + p); p += 4; + AssertIntGT((int)count, 0); + + for (i = 0; i < (int)count; i++) { + UINT32 tag = GetU32BE(gRsp + p); p += 4; + int wireSz = gRsp[p]; p += 1; + int selSz = (wireSz > 8) ? 8 : wireSz; + if (tag == TPM_PT_PCR_RESET_L0) { + memcpy(resetL0, gRsp + p, selSz); gotResetL0 = 1; + } + else if (tag == TPM_PT_PCR_RESET_L4) { + memcpy(resetL4, gRsp + p, selSz); gotResetL4 = 1; + } + else if (tag == TPM_PT_PCR_EXTEND_L0) { + memcpy(extendL0, gRsp + p, selSz); gotExtendL0 = 1; + } + else if (tag == TPM_PT_PCR_DRTM_RESET) { + memcpy(drtm, gRsp + p, selSz); gotDrtm = 1; + } + p += wireSz; /* advance past the select bytes */ + } + + AssertIntEQ(gotResetL0, 1); + AssertIntEQ(gotResetL4, 1); + AssertIntEQ(gotExtendL0, 1); + AssertIntEQ(gotDrtm, 1); + + /* RESET_L0: 16 and 23 set; 17 and 20 clear */ + AssertIntEQ(PcrSelHas(resetL0, 8, 16), 1); + AssertIntEQ(PcrSelHas(resetL0, 8, 23), 1); + AssertIntEQ(PcrSelHas(resetL0, 8, 17), 0); + AssertIntEQ(PcrSelHas(resetL0, 8, 20), 0); + /* RESET_L4: 17-22 set; 16 and 23 clear */ + AssertIntEQ(PcrSelHas(resetL4, 8, 17), 1); + AssertIntEQ(PcrSelHas(resetL4, 8, 22), 1); + AssertIntEQ(PcrSelHas(resetL4, 8, 16), 0); + AssertIntEQ(PcrSelHas(resetL4, 8, 23), 0); + /* EXTEND_L0: 0,16,23 set; 17 clear */ + AssertIntEQ(PcrSelHas(extendL0, 8, 0), 1); + AssertIntEQ(PcrSelHas(extendL0, 8, 16), 1); + AssertIntEQ(PcrSelHas(extendL0, 8, 17), 0); + /* DRTM_RESET: 17-22 set; 16 clear */ + AssertIntEQ(PcrSelHas(drtm, 8, 17), 1); + AssertIntEQ(PcrSelHas(drtm, 8, 22), 1); + AssertIntEQ(PcrSelHas(drtm, 8, 16), 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_PROPERTIES capability map:", 0); } static void test_fwtpm_pcr_event(void) @@ -10460,6 +10771,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getcap_commands(); test_fwtpm_getcap_properties(); test_fwtpm_getcap_pcrs(); + test_fwtpm_getcap_paging(); /* PCR operations */ test_fwtpm_pcr_read(); @@ -10469,6 +10781,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(); test_fwtpm_pcr_reset(); test_fwtpm_pcr_reset_locality_enforced(); + test_fwtpm_pcr_extend_locality_enforced(); + test_fwtpm_pcr_properties_capability(); test_fwtpm_pcr_event(); /* Clock */ diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 5f4da10b..2d80b35d 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -516,6 +516,37 @@ static void test_TPM2_Policy_NULL_Args(void) printf("Test TPM2: %-40s Passed\n", "Policy NULL Args:"); } +static void test_wolfTPM2_SetLocality(void) +{ + int rc = 0; + WOLFTPM2_DEV dev; + + XMEMSET(&dev, 0, sizeof(dev)); + + /* Argument validation. The Linux-kernel driver and Windows TBS backends + * return NOT_COMPILED_IN before validating args, so only assert + * BAD_FUNC_ARG on the SWTPM and built-in TIS backends. */ +#if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_WINAPI) + rc = wolfTPM2_SetLocality(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SetLocality(&dev, -1); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SetLocality(&dev, 5); + AssertIntEQ(rc, BAD_FUNC_ARG); +#endif + +#if defined(WOLFTPM_SWTPM) + /* SWTPM/mssim record-only path: records the locality for subsequent + * commands with no TIS handshake, so it needs no live connection. */ + rc = wolfTPM2_SetLocality(&dev, 2); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(dev.ctx.locality, 2); +#endif + (void)rc; + + printf("Test TPM Wrapper: %-40s Passed\n", "SetLocality args/SWTPM:"); +} + static void test_wolfTPM2_PolicyAuthValue_AuthOffset(void) { #if !defined(WOLFTPM2_NO_WOLFCRYPT) @@ -4274,7 +4305,7 @@ static void test_wolfTPM2_EccSignVerifyDig(WOLFTPM2_DEV* dev, * post-hoc skip-check can't catch it. Query capabilities up front. */ if (!test_tpm_alg_supported(hashAlg)) { printf("Hash alg 0x%x not supported by TPM... Skipping\n", hashAlg); - return; + goto exit; } /* -- Use TPM key to sign and verify with wolfCrypt -- */ @@ -4294,11 +4325,11 @@ static void test_wolfTPM2_EccSignVerifyDig(WOLFTPM2_DEV* dev, } if ((rc & TPM_RC_HASH) == TPM_RC_HASH) { printf("Hash type not supported... Skipping\n"); - return; + goto exit; } if ((rc & TPM_RC_CURVE) == TPM_RC_CURVE) { printf("Curve not supported... Skipping\n"); - return; + goto exit; } AssertIntEQ(rc, 0); @@ -4399,11 +4430,15 @@ static void test_wolfTPM2_EccSignVerifyDig(WOLFTPM2_DEV* dev, printf("Test TPM Wrapper: %-40s %s\n", nameBuf, rc == 0 ? "Passed" : "Failed"); +exit: #ifdef WOLF_CRYPTO_CB + /* Unregister on every path (incl. skips) so a leaked registration does + * not make the next SetCryptoDevCb return ALREADY_E on wolfSSL 5.9.2+. */ if (flags & FLAGS_USE_CRYPTO_CB) { wolfTPM2_ClearCryptoDevCb(dev, tpmDevId); } #endif + (void)tpmDevId; } static void test_wolfTPM2_EccSignVerify_All(WOLFTPM2_DEV* dev, @@ -6307,6 +6342,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_HashFinish_BufferTooSmall(); test_TPM2_PCRSel(); test_TPM2_Policy_NULL_Args(); + test_wolfTPM2_SetLocality(); test_wolfTPM2_PolicyAuthValue_AuthOffset(); test_wolfTPM2_SetAuthHandle_PolicyAuthOffset(); test_wolfTPM2_StartSession_SaltedEncryptAttrs(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index e4f16dc2..f2d99c35 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -678,7 +678,7 @@ typedef struct FWTPM_CTX { int lockoutAuthFailed; /* lockoutAuth lock; persisted (reboot-clears * only when clockless or lockoutRecovery==0) */ #endif - int activeLocality; + int activeLocality; /* locality of the command being processed */ UINT64 clockOffset; /* Clock offset set by ClockSet */ UINT32 resetCount; /* TPM Reset count, persisted across boots */ UINT32 restartCount; /* TPM Restart/Resume count, volatile */ @@ -797,6 +797,12 @@ typedef struct FWTPM_CTX { int spdmMode; struct WOLFSPDM_RESP_CTX* spdmRespCtx; #endif + +#ifdef WOLFTPM_FWTPM_TIS + /* TIS transport: locality that currently owns the interface (ACCESS active + * locality); 0-4 = owner, -1 = none. TIS layer only. */ + int tisLocality; +#endif } FWTPM_CTX; /* fwTPM SPDM mode bits - used with FWTPM_CTX.spdmMode. */ diff --git a/wolftpm/fwtpm/fwtpm_tis.h b/wolftpm/fwtpm/fwtpm_tis.h index dde42f20..0377ff89 100644 --- a/wolftpm/fwtpm/fwtpm_tis.h +++ b/wolftpm/fwtpm/fwtpm_tis.h @@ -117,13 +117,18 @@ typedef struct FWTPM_TIS_REGS { UINT32 version; /* Protocol version */ /* Register access request (client writes, server reads) */ - UINT32 reg_addr; /* Register offset (locality stripped) */ + UINT32 reg_addr; /* Full TIS address: base | offset | + * (locality << 12). Server extracts both. */ UINT32 reg_len; /* Transfer length in bytes */ BYTE reg_is_write; /* 1=write, 0=read */ BYTE reg_data[64]; /* Data for write or read result */ /* TIS register shadow state (server owns, client reads) */ - UINT32 access; /* TPM_ACCESS register */ + UINT32 access; /* Reserved/unused: the TPM_ACCESS register is + * now served dynamically per read from + * ctx->tisLocality (see + * FWTPM_TIS_HandleRegAccess). Kept for + * shared-memory ABI layout stability. */ UINT32 sts; /* TPM_STS register (low byte = status, * upper 16 bits = burst count) */ UINT32 int_enable; /* TPM_INT_ENABLE register */ diff --git a/wolftpm/tpm2_tis.h b/wolftpm/tpm2_tis.h index 28c10b41..c5f7bb98 100644 --- a/wolftpm/tpm2_tis.h +++ b/wolftpm/tpm2_tis.h @@ -34,9 +34,26 @@ #define WOLFTPM_LOCALITY_DEFAULT 0 #endif +/* Optional: WOLFTPM_TIS_RESET_STALE_LOCALITY makes TPM2_TIS_RequestLocality + * release any other active locality at startup so the default can be granted - + * recovers a wedge left when a prior session did not return to locality 0. Off + * by default: on a shared bus it could clear a locality another master holds, so + * enable it only for single-master setups (or use the nRST HAL to recover). */ + +/* Number of poll attempts when requesting a locality at runtime. A legitimate + * grant is near-instant, so this is kept small to fail fast when a locality + * cannot be granted (unlike TPM_TIMEOUT_TRIES used for chip startup). */ +#ifndef WOLFTPM_LOCALITY_TIMEOUT_TRIES +#define WOLFTPM_LOCALITY_TIMEOUT_TRIES 1000 +#endif + #define TPM_TIS_READ 0x80 #define TPM_TIS_WRITE 0x00 +/* A floating/undecoded bus reads back 0xFF; since that has both the active and + * valid ACCESS bits set it must not be mistaken for a granted locality. */ +#define TPM_TIS_ACCESS_INVALID 0xFFu + #define TPM_TIS_HEADER_SZ 4 #define TPM_TIS_READY_MASK 0x01 @@ -58,6 +75,8 @@ WOLFTPM_LOCAL int TPM2_TIS_WaitForStatus(TPM2_CTX* ctx, byte status, byte status WOLFTPM_LOCAL int TPM2_TIS_Status(TPM2_CTX* ctx, byte* status); WOLFTPM_LOCAL int TPM2_TIS_GetInfo(TPM2_CTX* ctx); WOLFTPM_LOCAL int TPM2_TIS_RequestLocality(TPM2_CTX* ctx, int timeout); +WOLFTPM_LOCAL int TPM2_TIS_RequestLocalityEx(TPM2_CTX* ctx, int locality, int timeout); +WOLFTPM_LOCAL int TPM2_TIS_ReleaseLocality(TPM2_CTX* ctx, int locality); WOLFTPM_LOCAL int TPM2_TIS_CheckLocality(TPM2_CTX* ctx, int locality, byte* access); WOLFTPM_LOCAL int TPM2_TIS_StartupWait(TPM2_CTX* ctx, int timeout); WOLFTPM_LOCAL int TPM2_TIS_Write(TPM2_CTX* ctx, word32 addr, const byte* value, word32 len); diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index 42e10502..d1893447 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -699,6 +699,11 @@ typedef int64_t INT64; #ifndef NUM_LOCALITIES #define NUM_LOCALITIES 1 #endif +#ifndef WOLFTPM_LOCALITY_MAX +/* Highest TPM locality index (localities 0-4 per TPM 2.0); not NUM_LOCALITIES, + * which is the platform locality count. */ +#define WOLFTPM_LOCALITY_MAX 4 +#endif #ifndef MAX_HANDLE_NUM #define MAX_HANDLE_NUM 3 #endif diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 47062d4e..108b3093 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -2749,6 +2749,32 @@ WOLFTPM_API int wolfTPM2_ReadPCR(WOLFTPM2_DEV* dev, */ WOLFTPM_API int wolfTPM2_ResetPCR(WOLFTPM2_DEV* dev, int pcrIndex); +/*! + \ingroup wolfTPM2_Wrappers + \brief Change the active TPM locality at runtime + \note Startup uses WOLFTPM_LOCALITY_DEFAULT (0); this switches so subsequent + commands run at the given locality (e.g. loc 2/3 to reset PCRs not + resettable at loc 0). For the built-in TIS driver it performs the ACCESS + handshake (request, then release-and-request if the TPM does not preempt, + restoring the old locality on failure); for the SWTPM/mssim transport it + records the locality (sent with each command). The Linux kernel driver and + Windows TBS own the locality and return NOT_COMPILED_IN. Return to locality + 0 when done. + + \return TPM_RC_SUCCESS: locality granted and now active + \return other: on the built-in TIS driver any non-success TPM_RC_* code (for + example TPM_RC_TIMEOUT) may be returned when the locality could not be + granted, so test rc != TPM_RC_SUCCESS rather than a specific code + \return NOT_COMPILED_IN: locality switching is not supported for this backend + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param locality integer value, specifying the TPM locality (0 to 4) + + \sa wolfTPM2_ResetPCR +*/ +WOLFTPM_API int wolfTPM2_SetLocality(WOLFTPM2_DEV* dev, int locality); + /*! \ingroup wolfTPM2_Wrappers \brief Extend a PCR register with a user provided digest