mirror of https://github.com/wolfSSL/wolfTPM.git
334 lines
9.7 KiB
C
334 lines
9.7 KiB
C
/* swtpm_uart_unit_tests.c
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfTPM.
|
|
*
|
|
* wolfTPM is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfTPM is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
/* Tests for the SWTPM UART receive path. A pseudo-terminal stands in for the
|
|
* serial device and a forked child plays the TPM, so neither hardware nor a
|
|
* simulator is required. tpm2_swtpm.c is compiled into this binary because
|
|
* TPM2_SWTPM_SendCommand is WOLFTPM_LOCAL.
|
|
*
|
|
* tests/include.am shortens the receive deadline and the per-read VTIME, so
|
|
* the timeout case does not take the production 60 seconds. */
|
|
|
|
/* posix_openpt, grantpt, unlockpt and ptsname. Other platforms declare them
|
|
* without a feature macro, and on macOS _XOPEN_SOURCE hides usleep. */
|
|
#if defined(__linux__)
|
|
#ifndef _XOPEN_SOURCE
|
|
#define _XOPEN_SOURCE 600
|
|
#endif
|
|
#ifndef _DEFAULT_SOURCE
|
|
#define _DEFAULT_SOURCE
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <wolftpm/tpm2.h>
|
|
#include <wolftpm/tpm2_swtpm.h>
|
|
#include <wolftpm/tpm2_packet.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#if defined(WOLFTPM_SWTPM) && defined(WOLFTPM_SWTPM_UART) && \
|
|
!defined(_WIN32) && defined(CLOCK_MONOTONIC)
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/resource.h>
|
|
|
|
/* Kept in step with the defaults in src/tpm2_swtpm.c, for a build that does
|
|
* not override them. tests/include.am shortens both. */
|
|
#ifndef WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS
|
|
#define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 60
|
|
#endif
|
|
#ifndef WOLFTPM_SWTPM_UART_VTIME
|
|
#define WOLFTPM_SWTPM_UART_VTIME 200
|
|
#endif
|
|
|
|
/* How the child behaves once it has read the command */
|
|
#define TEST_RSP_DELAYED 1 /* answer after several VTIME periods */
|
|
#define TEST_RSP_SILENT 2 /* hold the line open and say nothing */
|
|
#define TEST_RSP_HANGUP 3 /* drop the peer */
|
|
|
|
/* mssim framing: TPM_SEND_COMMAND, locality, command size */
|
|
#define TEST_CMD_HDR_SZ 9
|
|
#define TEST_CMD_SZ 12
|
|
#define TEST_RSP_SZ 10
|
|
/* response size, response, ack */
|
|
#define TEST_RSP_WIRE_SZ (4 + TEST_RSP_SZ + 4)
|
|
/* Well inside the deadline, and many VTIME periods at the test's VTIME */
|
|
#define TEST_DELAY_MS 1500
|
|
/* A receive that waits on poll() costs no measurable CPU. Retrying a read
|
|
* instead burns a whole core, so this is what catches a busy loop. */
|
|
#define TEST_MAX_CPU_SECS 0.5
|
|
|
|
static int gFailures = 0;
|
|
|
|
static double TestNowSec(void)
|
|
{
|
|
struct timespec ts;
|
|
double now = 0;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
|
now = (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0);
|
|
}
|
|
return now;
|
|
}
|
|
|
|
/* CPU seconds consumed by this process so far */
|
|
static double TestCpuSec(void)
|
|
{
|
|
struct rusage ru;
|
|
double cpu = 0;
|
|
|
|
if (getrusage(RUSAGE_SELF, &ru) == 0) {
|
|
cpu = (double)ru.ru_utime.tv_sec +
|
|
((double)ru.ru_utime.tv_usec / 1000000.0) +
|
|
(double)ru.ru_stime.tv_sec +
|
|
((double)ru.ru_stime.tv_usec / 1000000.0);
|
|
}
|
|
return cpu;
|
|
}
|
|
|
|
/* Read exactly len bytes. Returns 0 on success. */
|
|
static int TestReadFull(int fd, byte* buf, size_t len)
|
|
{
|
|
ssize_t got;
|
|
int rc = 0;
|
|
|
|
while (len > 0) {
|
|
got = read(fd, buf, len);
|
|
if (got <= 0) {
|
|
rc = -1;
|
|
break;
|
|
}
|
|
buf += got;
|
|
len -= (size_t)got;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Runs in the forked child, which is the only holder of the master side. */
|
|
static void TestServeTpm(int master, int behavior)
|
|
{
|
|
byte hdr[TEST_CMD_HDR_SZ];
|
|
byte cmd[TEST_CMD_SZ];
|
|
byte rsp[TEST_RSP_WIRE_SZ];
|
|
UINT32 cmdSz;
|
|
|
|
if (TestReadFull(master, hdr, sizeof(hdr)) != 0) {
|
|
_exit(1);
|
|
}
|
|
cmdSz = ((UINT32)hdr[5] << 24) | ((UINT32)hdr[6] << 16) |
|
|
((UINT32)hdr[7] << 8) | (UINT32)hdr[8];
|
|
if (cmdSz != TEST_CMD_SZ || TestReadFull(master, cmd, cmdSz) != 0) {
|
|
_exit(1);
|
|
}
|
|
|
|
if (behavior == TEST_RSP_HANGUP) {
|
|
close(master);
|
|
_exit(0);
|
|
}
|
|
if (behavior == TEST_RSP_SILENT) {
|
|
/* Outlast the deadline without closing, so the client has to time
|
|
* out rather than see a hangup. */
|
|
sleep(WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS + 2);
|
|
_exit(0);
|
|
}
|
|
|
|
/* TEST_RSP_DELAYED: answer after far more than one VTIME period */
|
|
usleep(TEST_DELAY_MS * 1000);
|
|
XMEMSET(rsp, 0, sizeof(rsp));
|
|
rsp[3] = TEST_RSP_SZ; /* response size */
|
|
rsp[4] = 0x80; rsp[5] = 0x01; /* TPM_ST_NO_SESSIONS */
|
|
rsp[9] = TEST_RSP_SZ; /* size inside the response header */
|
|
/* the remaining bytes are TPM_RC_SUCCESS and a zero ack */
|
|
if (write(master, rsp, sizeof(rsp)) != (ssize_t)sizeof(rsp)) {
|
|
_exit(1);
|
|
}
|
|
/* Stay open for the TPM_SESSION_END that follows the response */
|
|
(void)TestReadFull(master, hdr, 4);
|
|
_exit(0);
|
|
}
|
|
|
|
/* Drive one command over a pty and report what the client did. Returns the
|
|
* result code, with the wall-clock time in elapsed and the CPU time the call
|
|
* cost in cpu. */
|
|
static int TestSendOverPty(int behavior, double* elapsed, double* cpu)
|
|
{
|
|
TPM2_CTX ctx;
|
|
TPM2_Packet packet;
|
|
byte buf[256];
|
|
char devPath[64];
|
|
const char* ptsPath;
|
|
double start;
|
|
double cpuStart;
|
|
pid_t pid;
|
|
int master;
|
|
int status = 0;
|
|
int rc;
|
|
|
|
*elapsed = 0;
|
|
*cpu = 0;
|
|
|
|
master = posix_openpt(O_RDWR | O_NOCTTY);
|
|
if (master < 0 || grantpt(master) != 0 || unlockpt(master) != 0) {
|
|
printf("pty setup failed\n");
|
|
return TPM_RC_FAILURE;
|
|
}
|
|
ptsPath = ptsname(master);
|
|
if (ptsPath == NULL) {
|
|
printf("ptsname failed\n");
|
|
close(master);
|
|
return TPM_RC_FAILURE;
|
|
}
|
|
strncpy(devPath, ptsPath, sizeof(devPath) - 1);
|
|
devPath[sizeof(devPath) - 1] = '\0';
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
printf("fork failed\n");
|
|
close(master);
|
|
return TPM_RC_FAILURE;
|
|
}
|
|
if (pid == 0) {
|
|
TestServeTpm(master, behavior);
|
|
_exit(1);
|
|
}
|
|
|
|
/* The child now owns the master side, so closing it here means a child
|
|
* exit really does hang the line up. */
|
|
close(master);
|
|
setenv("TPM2_SWTPM_HOST", devPath, 1);
|
|
|
|
XMEMSET(&ctx, 0, sizeof(ctx));
|
|
ctx.tcpCtx.fd = -1;
|
|
XMEMSET(&packet, 0, sizeof(packet));
|
|
XMEMSET(buf, 0, sizeof(buf));
|
|
buf[0] = 0x80; buf[1] = 0x01; /* TPM_ST_NO_SESSIONS */
|
|
buf[5] = TEST_CMD_SZ;
|
|
packet.buf = buf;
|
|
packet.size = (int)sizeof(buf);
|
|
packet.pos = TEST_CMD_SZ;
|
|
|
|
start = TestNowSec();
|
|
cpuStart = TestCpuSec();
|
|
rc = TPM2_SWTPM_SendCommand(&ctx, &packet);
|
|
*elapsed = TestNowSec() - start;
|
|
*cpu = TestCpuSec() - cpuStart;
|
|
|
|
TPM2_SwtpmClose(&ctx);
|
|
/* The silent child is still sleeping out its timer */
|
|
kill(pid, SIGKILL);
|
|
waitpid(pid, &status, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void TestCheck(const char* name, int pass, double elapsed, double cpu)
|
|
{
|
|
printf("Test SWTPM UART: %-40s %s (%.2fs wall, %.2fs cpu)\n", name,
|
|
pass ? "Passed" : "FAILED", elapsed, cpu);
|
|
if (!pass) {
|
|
gFailures++;
|
|
}
|
|
}
|
|
|
|
/* A response arriving many VTIME periods late must still be accepted. Before
|
|
* the poll based deadline, the first zero byte read was treated as EOF and
|
|
* any command slower than one period failed. */
|
|
static void test_SwTpmUart_LateResponse(void)
|
|
{
|
|
double elapsed = 0;
|
|
double cpu = 0;
|
|
int rc = TestSendOverPty(TEST_RSP_DELAYED, &elapsed, &cpu);
|
|
|
|
TestCheck("late response accepted",
|
|
rc == TPM_RC_SUCCESS &&
|
|
elapsed >= (((double)TEST_DELAY_MS / 1000.0) - 0.1) &&
|
|
elapsed < (double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS &&
|
|
cpu < TEST_MAX_CPU_SECS,
|
|
elapsed, cpu);
|
|
}
|
|
|
|
/* Silence must fail at the cumulative deadline, not at some multiple of it. */
|
|
static void test_SwTpmUart_Timeout(void)
|
|
{
|
|
double elapsed = 0;
|
|
double cpu = 0;
|
|
int rc = TestSendOverPty(TEST_RSP_SILENT, &elapsed, &cpu);
|
|
|
|
TestCheck("silence times out at the deadline",
|
|
rc != TPM_RC_SUCCESS &&
|
|
elapsed >= ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS - 0.5) &&
|
|
elapsed <= ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS + 1.0) &&
|
|
cpu < TEST_MAX_CPU_SECS,
|
|
elapsed, cpu);
|
|
}
|
|
|
|
/* A hung up line returns a zero byte read with no delay. Retrying that read
|
|
* spins on the CPU until the deadline, so it has to fail promptly instead. */
|
|
static void test_SwTpmUart_Hangup(void)
|
|
{
|
|
double elapsed = 0;
|
|
double cpu = 0;
|
|
int rc = TestSendOverPty(TEST_RSP_HANGUP, &elapsed, &cpu);
|
|
|
|
TestCheck("hangup fails promptly",
|
|
rc != TPM_RC_SUCCESS &&
|
|
elapsed < ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS / 2.0) &&
|
|
cpu < TEST_MAX_CPU_SECS,
|
|
elapsed, cpu);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
printf("SWTPM UART tests, deadline %d secs, VTIME %d\n",
|
|
WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS, WOLFTPM_SWTPM_UART_VTIME);
|
|
|
|
test_SwTpmUart_LateResponse();
|
|
test_SwTpmUart_Timeout();
|
|
test_SwTpmUart_Hangup();
|
|
|
|
if (gFailures != 0) {
|
|
printf("SWTPM UART tests: %d failed\n", gFailures);
|
|
return 1;
|
|
}
|
|
printf("SWTPM UART tests: all passed\n");
|
|
return 0;
|
|
}
|
|
|
|
#else /* !WOLFTPM_SWTPM_UART */
|
|
|
|
int main(void)
|
|
{
|
|
printf("SWTPM UART tests skipped\n");
|
|
return 0;
|
|
}
|
|
|
|
#endif
|