Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/make-test-swtpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,13 @@ jobs:
wolftpm_config: --disable-provisioning --disable-fwtpm
needs_swtpm: false

# SWTPM UART transport (compile-only; no UART peer in CI)
# SWTPM UART transport. No UART peer in CI, so the receive tests
# use a pseudo-terminal. make check is not usable here because
# unit.test needs a TPM.
- name: swtpm-uart
wolftpm_config: --enable-swtpm=uart --disable-fwtpm
needs_swtpm: false
test_command: "./tests/swtpm_uart_unit.test"

# Symmetric encryption
- name: symmetric
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ tests/fwtpm_unit.log
tests/fwtpm_unit.test
tests/fwtpm_hal_unit.test
tests/fwtpm_hal_unit.log
tests/swtpm_uart_unit.test
tests/swtpm_uart_unit.log
tests/fwtpm_check.sh.log
tests/packet_auth.test
tests/packet_auth.log
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ AM_CONDITIONAL([BUILD_INFINEON], [test "x$ENABLED_INFINEON" != "xno"])
AM_CONDITIONAL([BUILD_INFINEON_SPDM], [test "x$BUILD_SPDM_INFINEON" = "xyes"])
AM_CONDITIONAL([BUILD_DEVTPM], [test "x$ENABLED_DEVTPM" = "xyes"])
AM_CONDITIONAL([BUILD_SWTPM], [test "x$ENABLED_SWTPM" = "xyes" || test "x$ENABLED_SWTPM" = "xuart"])
AM_CONDITIONAL([BUILD_SWTPM_UART], [test "x$ENABLED_SWTPM" = "xuart"])
AM_CONDITIONAL([BUILD_WINAPI], [test "x$ENABLED_WINAPI" = "xyes"])
AM_CONDITIONAL([BUILD_NUVOTON], [test "x$ENABLED_NUVOTON" = "xyes"])
AM_CONDITIONAL([BUILD_NATIONS], [test "x$ENABLED_NATIONS" = "xyes"])
Expand Down
4 changes: 3 additions & 1 deletion docs/system-interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ Build options:
* `WOLFTPM_SWTPM_UART`: use the UART serial transport (set automatically by `--enable-swtpm=uart`)
* `TPM2_SWTPM_HOST`: the serial device path (default is `/dev/ttyACM0` on Linux and `/dev/cu.usbmodem` on macOS). It can be overridden at runtime with the `TPM2_SWTPM_HOST` environment variable.
* `TPM2_SWTPM_PORT`: the baud rate (default is 115200)
* `WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS`: cumulative wall-clock timeout for a single receive (default is 60)
* `WOLFTPM_SWTPM_UART_VTIME`: per-read termios `VTIME`, in tenths of a second (default is 200, so 20 seconds)

The UART transport uses the same mssim protocol as the socket transport. The serial port is configured as 8N1 raw mode with no flow control. Like the socket transport, the serial port file descriptor is kept open across commands (no reconnect per command). Both transports close the connection during `wolfTPM2_Cleanup`. On the socket transport, any transmit or receive failure also closes the connection so the next command reconnects. The UART transport closes only when the per-command `TPM_SESSION_END` write fails.
The UART transport uses the same mssim protocol as the socket transport. The serial port is configured as 8N1 raw mode with no flow control. A receive waits for the device with `poll()` until `WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS` elapses, so a command taking longer than one `VTIME` period, such as RSA key generation on an MCU, still completes, while a device hangup fails immediately instead of waiting out the timeout. Like the socket transport, the serial port file descriptor is kept open across commands (no reconnect per command). Both transports close the connection during `wolfTPM2_Cleanup`. On the socket transport, any transmit or receive failure also closes the connection so the next command reconnects. The UART transport closes only when the per-command `TPM_SESSION_END` write fails.

#### Security note: environment variable override

Expand Down
137 changes: 108 additions & 29 deletions src/tpm2_swtpm.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,23 @@
#include <termios.h>
#include <sys/stat.h>
#include <time.h>
#include <poll.h>
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
/* Cumulative wall-clock timeout for SwTpmReceive. Defends against trickle
* senders that repeatedly reset the per-read VMIN/VTIME timer. */
/* Cumulative wall-clock timeout for SwTpmReceive. Bounds the total wait for
* a response, which can exceed one VTIME period (e.g. RSA key generation on
* an MCU), and defends against trickle senders that repeatedly reset the
* per-read VMIN/VTIME timer. */
#ifndef WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS
#define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 30
#define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 60
#endif
/* Per-read timeout in tenths of a second, used with VMIN=0 */
#ifndef WOLFTPM_SWTPM_UART_VTIME
#define WOLFTPM_SWTPM_UART_VTIME 200
#endif
#endif

Expand Down Expand Up @@ -186,25 +193,113 @@ static TPM_RC SwTpmTransmit(TPM2_CTX* ctx, const void* buffer, ssize_t bufSz)
return rc;
}

#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC)
/* Monotonic milliseconds. Returns 0 on success. */
static int SwTpmUartNowMs(uint64_t* nowMs)
{
int rc = -1;
struct timespec ts;

if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
*nowMs = ((uint64_t)ts.tv_sec * 1000) +
((uint64_t)ts.tv_nsec / 1000000);
rc = 0;
}
return rc;
}

/* Wait for the UART to become readable, bounded by the time left on the
* cumulative receive deadline. A hung-up TTY returns a zero-byte read with
* no delay, so a hangup is reported rather than retried. */
static TPM_RC SwTpmUartWait(int fd, uint64_t deadlineMs)
{
TPM_RC rc = TPM_RC_FAILURE;
struct pollfd pfd;
uint64_t nowMs = 0;
int prc;

pfd.fd = fd;
pfd.events = POLLIN;

for (;;) {
if (SwTpmUartNowMs(&nowMs) != 0) {
break;
}
if (nowMs >= deadlineMs) {
#ifdef DEBUG_WOLFTPM
printf("SwTpmReceive: cumulative UART timeout after %ds\n",
WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS);
#endif
break;
}
pfd.revents = 0;
prc = poll(&pfd, 1, (int)(deadlineMs - nowMs));
if (prc < 0) {
if (errno == EINTR) {
continue;
}
#ifdef DEBUG_WOLFTPM
printf("Failed to poll TPM UART %d, got errno %d = %s\n",
fd, errno, strerror(errno));
#endif
break;
}
if (prc == 0) {
#ifdef DEBUG_WOLFTPM
printf("SwTpmReceive: cumulative UART timeout after %ds\n",
WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS);
#endif
break;
}
if ((pfd.revents & POLLIN) != 0) {
rc = TPM_RC_SUCCESS;
break;
}
#ifdef DEBUG_WOLFTPM
printf("TPM UART hangup, poll events 0x%x\n", pfd.revents);
#endif
break;
}

return rc;
}
#endif /* WOLFTPM_SWTPM_UART && CLOCK_MONOTONIC */

static TPM_RC SwTpmReceive(TPM2_CTX* ctx, void* buffer, size_t rxSz)
{
TPM_RC rc = TPM_RC_SUCCESS;
ssize_t wrc = 0;
size_t bytes_remaining = rxSz;
char* ptr = (char*)buffer;
#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC)
struct timespec start, now;
int haveStart = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == 0) {
haveStart = 1;
}
uint64_t nowMs = 0;
uint64_t deadlineMs = 0;
int haveDeadline = 0;
#endif

if (ctx == NULL || !SWTPM_FD_IS_VALID(ctx->tcpCtx.fd) || buffer == NULL) {
return BAD_FUNC_ARG;
}

#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC)
if (SwTpmUartNowMs(&nowMs) == 0) {
deadlineMs = nowMs +
((uint64_t)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS * 1000);
haveDeadline = 1;
}
#endif

while (bytes_remaining > 0) {
#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC)
/* Wait within the cumulative deadline, so a response that takes
* longer than one VTIME period is not treated as EOF */
if (haveDeadline) {
rc = SwTpmUartWait(ctx->tcpCtx.fd, deadlineMs);
if (rc != TPM_RC_SUCCESS) {
break;
}
}
#endif
wrc = SWTPM_READ(ctx->tcpCtx.fd, ptr, bytes_remaining);
if (wrc < 0) {
if (SWTPM_SHOULD_RETRY()) {
Expand Down Expand Up @@ -233,22 +328,6 @@ static TPM_RC SwTpmReceive(TPM2_CTX* ctx, void* buffer, size_t rxSz)
wrc, bytes_remaining);
#endif

#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC)
/* Enforce cumulative timeout so a trickle sender cannot reset
* VMIN/VTIME indefinitely. */
if (haveStart && bytes_remaining > 0 &&
clock_gettime(CLOCK_MONOTONIC, &now) == 0 &&
(now.tv_sec - start.tv_sec) >
WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS) {
#ifdef DEBUG_WOLFTPM
printf("SwTpmReceive: cumulative UART timeout after %lds "
"(%zu bytes remaining)\n",
(long)(now.tv_sec - start.tv_sec), bytes_remaining);
#endif
rc = TPM_RC_FAILURE;
break;
}
#endif
}

return rc;
Expand Down Expand Up @@ -359,12 +438,12 @@ static TPM_RC SwTpmConnect(TPM2_CTX* ctx, const char* host, const char* port)
tty.c_oflag &= ~OPOST;
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);

/* Read with overall timeout.
* RSA key generation on embedded targets can take 10+ seconds,
* so use a generous timeout. With VMIN=0 and VTIME>0, read()
* returns after the timeout even if no byte is received. */
/* Per-read timeout. With VMIN=0 and VTIME>0, read() returns after the
* timeout even if no byte is received; SwTpmReceive polls up to
* WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS, since RSA key generation on
* embedded targets can take longer than one period. */
tty.c_cc[VMIN] = 0; /* allow timeout without requiring first byte */
tty.c_cc[VTIME] = 200; /* 20 second timeout (tenths of seconds) */
tty.c_cc[VTIME] = WOLFTPM_SWTPM_UART_VTIME;

if (tcsetattr(fd, TCSANOW, &tty) != 0) {
close(fd);
Expand Down
18 changes: 18 additions & 0 deletions tests/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ tests_fwtpm_unit_test_CPPFLAGS = \
tests_fwtpm_unit_test_LDADD = $(LIB_STATIC_ADD)
endif

# SWTPM UART receive tests. Driven over a pseudo-terminal, so no hardware or
# simulator is needed, but make check cannot run in a UART configuration
# (unit.test needs a TPM), so the swtpm-uart CI job runs this directly.
# tpm2_swtpm.c is compiled in because TPM2_SWTPM_SendCommand is WOLFTPM_LOCAL.
if BUILD_SWTPM_UART
noinst_PROGRAMS += tests/swtpm_uart_unit.test
tests_swtpm_uart_unit_test_SOURCES = \
tests/swtpm_uart_unit_tests.c \
src/tpm2_swtpm.c \
src/tpm2_packet.c \
src/tpm2_util.c
# Shorten the deadline and the per-read period so the timeout case is quick
tests_swtpm_uart_unit_test_CFLAGS = \
-DWOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS=3 \
-DWOLFTPM_SWTPM_UART_VTIME=1 $(AM_CFLAGS)
tests_swtpm_uart_unit_test_LDADD = $(LIB_STATIC_ADD)
endif

if BUILD_FWTPM_TIS
check_PROGRAMS += tests/fwtpm_hal_unit.test
noinst_PROGRAMS += tests/fwtpm_hal_unit.test
Expand Down
Loading
Loading