From c2553a2923daa216888aa3661b9c2d2f907ff8e0 Mon Sep 17 00:00:00 2001 From: David Garske Date: Mon, 2 Nov 2026 13:49:55 -0800 Subject: [PATCH] fwtpm: send the mssim reply as one segment and disable Nagle --- src/fwtpm/fwtpm_io.c | 122 ++++++++++++++++++++++++++++++---- tests/fwtpm_io_test_adapter.c | 9 +++ tests/fwtpm_unit_tests.c | 47 +++++++++++++ 3 files changed, 165 insertions(+), 13 deletions(-) diff --git a/src/fwtpm/fwtpm_io.c b/src/fwtpm/fwtpm_io.c index d9ffd67b..c3d45e20 100644 --- a/src/fwtpm/fwtpm_io.c +++ b/src/fwtpm/fwtpm_io.c @@ -61,6 +61,14 @@ #ifndef _WIN32 #include #include +#include /* TCP_NODELAY */ +#endif + +/* Scatter/gather send, so one mssim reply is one segment */ +#if !defined(_WIN32) && !defined(WOLFTPM_ZEPHYR) && \ + !defined(FWTPM_NO_WRITEV) + #define FWTPM_HAVE_WRITEV + #include #endif /* Async-signal-safe stop flag. Set by FWTPM_IO_RequestStop() (callable from @@ -145,6 +153,49 @@ static int SocketSend(FWTPM_CTX* ctx, SOCKET_T fd, const void* buf, int sz) return TPM_RC_SUCCESS; } +#ifdef FWTPM_HAVE_WRITEV +/* Send an iovec set, advancing over partial writes. Modifies iov. */ +static int SocketSendVec(FWTPM_CTX* ctx, SOCKET_T fd, struct iovec* iov, + int cnt) +{ + ssize_t sent; + int i = 0; + + while (i < cnt) { + int rc = FWTPM_Clock_Update(ctx); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_NV_RATE) return rc; + rc = SocketWait(fd, 1); + if (rc == 0) { + if (g_stopRequested) return TPM_RC_FAILURE; + continue; + } + sent = (rc < 0) ? -1 : writev(fd, &iov[i], cnt - i); + if (sent < 0) { + if (errno == EINTR) { + continue; + } + #ifdef DEBUG_WOLFTPM + printf("fwTPM: writev error %d\n", errno); + #endif + return TPM_RC_FAILURE; + } + if (sent == 0) { + /* No progress on a blocking socket with bytes outstanding */ + return TPM_RC_FAILURE; + } + while (i < cnt && (size_t)sent >= iov[i].iov_len) { + sent -= (ssize_t)iov[i].iov_len; + i++; + } + if (i < cnt && sent > 0) { + iov[i].iov_base = (char*)iov[i].iov_base + sent; + iov[i].iov_len -= (size_t)sent; + } + } + return TPM_RC_SUCCESS; +} +#endif /* FWTPM_HAVE_WRITEV */ + static int SocketRecv(FWTPM_CTX* ctx, SOCKET_T fd, void* buf, int sz) { char* ptr = (char*)buf; @@ -260,6 +311,61 @@ static SOCKET_T CreateListenSocket(int port) return fd; } +/* Disable Nagle, which would hold the small mssim acknowledgement until the + * peer's delayed ACK. Advisory: a failure costs latency, not correctness. */ +static void SetSocketNoDelay(SOCKET_T fd) +{ +#ifdef TCP_NODELAY + int optval = 1; + + /* Winsock takes a char* here where POSIX takes a void*. */ + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (const char*)&optval, sizeof(optval)) != 0) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM: setsockopt(TCP_NODELAY) failed\n"); + #endif + } +#else + (void)fd; +#endif +} + +/* mssim reply: size(4) + response + ack(4), as one segment where the + * platform allows it. A custom HAL has no vectored form, so it keeps the + * three sequential sends. */ +static int SendMssimReply(FWTPM_CTX* ctx, SOCKET_T clientFd, + const byte* rsp, int rspSize) +{ + int rc; + UINT32 sizeVal = FwTpmSwapU32((UINT32)rspSize); + UINT32 ackVal = FwTpmSwapU32(0); /* ack = 0 (success) */ +#ifdef FWTPM_HAVE_WRITEV + struct iovec iov[3]; + + if (ctx->ioHal.wait == NULL && clientFd != FWTPM_INVALID_FD) { + iov[0].iov_base = &sizeVal; + iov[0].iov_len = sizeof(sizeVal); + iov[1].iov_base = (void*)rsp; + iov[1].iov_len = (size_t)rspSize; + iov[2].iov_base = &ackVal; + iov[2].iov_len = sizeof(ackVal); + return SocketSendVec(ctx, clientFd, iov, 3); + } +#endif + + rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, &sizeVal, + (int)sizeof(sizeVal)); + if (rc == TPM_RC_SUCCESS) { + rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, rsp, + rspSize); + } + if (rc == TPM_RC_SUCCESS) { + rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, &ackVal, + (int)sizeof(ackVal)); + } + return rc; +} + /* Build a minimal TPM error response using TPM2_Packet API */ static int BuildErrorResponse(byte* rspBuf, UINT16 tag, TPM_RC rc) { @@ -428,7 +534,6 @@ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality, int rc; int rspSize = 0; int procRc; - UINT32 netVal; int dispatched = 0; #ifdef WOLFTPM_SPDM_RESPONDER UINT16 firstTag = (cmdSize >= 2) ? FwLoadU16BE(ctx->cmdBuf) : 0; @@ -494,18 +599,7 @@ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality, ctx->rspBuf, rspSize); } else { - /* mssim protocol: size(4) + response + ack(4) */ - netVal = FwTpmSwapU32((UINT32)rspSize); - rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, &netVal, 4); - if (rc == TPM_RC_SUCCESS) { - rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, - ctx->rspBuf, rspSize); - } - if (rc == TPM_RC_SUCCESS) { - netVal = FwTpmSwapU32(0); /* ack = 0 (success) */ - rc = TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, - &netVal, 4); - } + rc = SendMssimReply(ctx, clientFd, ctx->rspBuf, rspSize); } return rc; @@ -974,6 +1068,7 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (FD_ISSET(ctx->io.platListenFd, &readFds)) { SOCKET_T newFd = accept(ctx->io.platListenFd, NULL, NULL); if (newFd != FWTPM_INVALID_FD) { + SetSocketNoDelay(newFd); if (platFd != FWTPM_INVALID_FD) { #ifdef DEBUG_WOLFTPM printf("fwTPM: platform connection replaced\n"); @@ -1008,6 +1103,7 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (FD_ISSET(ctx->io.listenFd, &readFds)) { SOCKET_T newFd = accept(ctx->io.listenFd, NULL, NULL); if (newFd != FWTPM_INVALID_FD) { + SetSocketNoDelay(newFd); for (i = 0; i < FWTPM_MAX_COMMAND_CLIENTS; i++) { if (cmdFds[i] == FWTPM_INVALID_FD) { cmdFds[i] = newFd; diff --git a/tests/fwtpm_io_test_adapter.c b/tests/fwtpm_io_test_adapter.c index 397d90e4..6ca11963 100644 --- a/tests/fwtpm_io_test_adapter.c +++ b/tests/fwtpm_io_test_adapter.c @@ -23,3 +23,12 @@ int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, { return HandleMssimSignal(ctx, clientFd, tssCmd); } + +int FWTPM_IO_TestSendMssimReply(FWTPM_CTX* ctx, SOCKET_T clientFd, + const byte* rsp, int rspSize); + +int FWTPM_IO_TestSendMssimReply(FWTPM_CTX* ctx, SOCKET_T clientFd, + const byte* rsp, int rspSize) +{ + return SendMssimReply(ctx, clientFd, rsp, rspSize); +} diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 5c7b723f..b5846791 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -65,6 +65,8 @@ #ifdef WOLFTPM_FWTPM_UNIT_TEST int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, UINT32 tssCmd); +int FWTPM_IO_TestSendMssimReply(FWTPM_CTX* ctx, SOCKET_T clientFd, + const byte* rsp, int rspSize); #endif #if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(HAVE_HASHDRBG) @@ -20399,6 +20401,50 @@ static void test_fwtpm_mssim_hash_data_framing(void) fwtpm_pass("MSSIM unsupported hash-data framing:", 0); } +/* An mssim reply is size(4) + response + ack(4). Whether it goes out as one + * vectored write or three sequential ones, the byte stream on the wire must + * be identical and in that order. Builds with FWTPM_NO_WRITEV exercise the + * sequential path through this same test. */ +static void test_fwtpm_mssim_reply_framing(void) +{ + FWTPM_CTX ctx; + byte rsp[2048]; + byte wire[4 + sizeof(rsp) + 4]; + int sockets[2]; + size_t total = 4 + sizeof(rsp) + 4; + size_t got = 0; + ssize_t n; + word32 i; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(wire, 0, sizeof(wire)); + for (i = 0; i < (word32)sizeof(rsp); i++) { + rsp[i] = (byte)(i & 0xFF); + } + + AssertIntEQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), 0); + AssertIntEQ(FWTPM_IO_TestSendMssimReply(&ctx, sockets[0], rsp, + (int)sizeof(rsp)), TPM_RC_SUCCESS); + + while (got < total) { + n = read(sockets[1], wire + got, total - got); + if (n <= 0) { + break; + } + got += (size_t)n; + } + AssertIntEQ((int)got, (int)total); + + /* size, then the response bytes unchanged, then a zero ack */ + AssertIntEQ((int)GetU32BE(wire), (int)sizeof(rsp)); + AssertIntEQ(XMEMCMP(wire + 4, rsp, sizeof(rsp)), 0); + AssertIntEQ((int)GetU32BE(wire + 4 + sizeof(rsp)), 0); + + (void)close(sockets[1]); + (void)close(sockets[0]); + fwtpm_pass("MSSIM reply framing:", 0); +} + static void test_fwtpm_mssim_invalid_fd(void) { FWTPM_CTX ctx; @@ -22632,6 +22678,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) #if defined(WOLFTPM_FWTPM_UNIT_TEST) && !defined(_WIN32) test_fwtpm_mssim_hash_data_framing(); + test_fwtpm_mssim_reply_framing(); test_fwtpm_mssim_invalid_fd(); #endif #ifdef WOLFTPM_FWTPM_UNIT_TEST