diff --git a/Makefile.am b/Makefile.am index 8af86fd3e..6e045877a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -346,6 +346,24 @@ src_tss2_tcti_libtss2_tcti_swtpm_la_SOURCES = \ endif # ENABLE_TCTI_SWTPM EXTRA_DIST += lib/tss2-tcti-swtpm.map lib/tss2-tcti-swtpm.def src/tss2-tcti/tss2-tcti-swtpm.vcxproj +# tcti library for swtpm +#if ENABLE_TCTI_START_SIM +libtss2_tcti_start_sim = src/tss2-tcti/libtss2-tcti-start-sim.la +tss2_HEADERS += $(srcdir)/include/tss2/tss2_tcti_start_sim.h +lib_LTLIBRARIES += $(libtss2_tcti_start_sim) +pkgconfig_DATA += lib/tss2-tcti-start-sim.pc + +if HAVE_LD_VERSION_SCRIPT +src_tss2_tcti_libtss2_tcti_start_sim_la_LDFLAGS = -Wl,--version-script=$(srcdir)/lib/tss2-tcti-start-sim.map +endif # HAVE_LD_VERSION_SCRIPT +src_tss2_tcti_libtss2_tcti_start_sim_la_LIBADD = $(libtss2_mu) $(libutil) $(libutilio) +src_tss2_tcti_libtss2_tcti_start_sim_la_SOURCES = \ + src/tss2-tcti/tcti-common.c \ + src/tss2-tcti/tcti-start-sim.c \ + src/tss2-tcti/tcti-start-sim.h +#endif # ENABLE_TCTI_START_SIM +EXTRA_DIST += lib/tss2-tcti-start-sim.map lib/tss2-tcti-start-sim.def src/tss2-tcti/tss2-tcti-start-sim.vcxproj + # tcti library for Microsoft TPM2 simulator if ENABLE_TCTI_MSSIM libtss2_tcti_mssim = src/tss2-tcti/libtss2-tcti-mssim.la diff --git a/configure.ac b/configure.ac index e2d579b8c..bd51f0d80 100644 --- a/configure.ac +++ b/configure.ac @@ -272,6 +272,13 @@ AC_ARG_ENABLE([tcti-swtpm], AM_CONDITIONAL([ENABLE_TCTI_SWTPM], [test "x$enable_tcti_swtpm" != xno]) AS_IF([test "x$enable_tcti_swtpm" = "xyes"], [AC_DEFINE([TCTI_SWTPM],[1], [TCTI FOR SWTPM])]) +AC_ARG_ENABLE([tcti-start-sim], + [AS_HELP_STRING([--disable-tcti-start-sim], + [don't build the tcti-start-sim module])],, + [enable_tcti_start_sim=yes]) +AM_CONDITIONAL([ENABLE_TCTI_START_SIM], [test "x$enable_tcti_start_sim" != xno]) +AS_IF([test "x$enable_tcti_start_sim" = "xyes"], [AC_DEFINE([TCTI_START_SIM],[1], [TCTI FOR STARTING THE SIMULATOR])]) + AC_ARG_ENABLE([tcti-pcap], [AS_HELP_STRING([--disable-tcti-pcap], [don't build the tcti-pcap module])],, @@ -456,6 +463,7 @@ AM_CONDITIONAL([DEVICEMANDATORY],[test "x$enable_device_mandatory" = "xyes"]) # enable integration tests and check for simulator binary # +# TODO migrate and remove --with-device AC_ARG_WITH([integrationtcti], [AS_HELP_STRING([--with-integrationtcti=],[TCTI used for testing])], [AS_IF([test x"$with_device_set" = "xyes"], diff --git a/include/tss2/tss2_tcti_start_sim.h b/include/tss2/tss2_tcti_start_sim.h new file mode 100644 index 000000000..5c1048333 --- /dev/null +++ b/include/tss2/tss2_tcti_start_sim.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2024, Infineon Technologies AG + * All rights reserved. + */ +#ifndef TSS2_TCTI_START_SIM_H +#define TSS2_TCTI_START_SIM_H + +#include "tss2_tcti.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TSS2_RC Tss2_Tcti_Start_Sim_Init ( + TSS2_TCTI_CONTEXT *tctiContext, + size_t *size, + const char *conf); + +#ifdef __cplusplus +} +#endif + +#endif /* TSS2_TCTI_START_SIM_H */ diff --git a/lib/tss2-tcti-start-sim.map b/lib/tss2-tcti-start-sim.map new file mode 100644 index 000000000..8415a933a --- /dev/null +++ b/lib/tss2-tcti-start-sim.map @@ -0,0 +1,8 @@ +{ + global: + Tss2_Tcti_Info; + Tss2_Tcti_Start_Sim_Init; + Tss2_Tcti_Start_Sim_Reset; + local: + *; +}; diff --git a/lib/tss2-tcti-start-sim.pc.in b/lib/tss2-tcti-start-sim.pc.in new file mode 100644 index 000000000..7d44538f7 --- /dev/null +++ b/lib/tss2-tcti-start-sim.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: tss2-tcti-start-sim +Description: TCTI library for starting a TPM simulator process. +URL: https://github.com/tpm2-software/tpm2-tss +Version: @VERSION@ +Requires.private: tss2-mu +Cflags: -I${includedir} -I${includedir}/tss2 +Libs: -ltss2-tcti-start-sim -L${libdir} diff --git a/src/tss2-tcti/tcti-start-sim.c b/src/tss2-tcti/tcti-start-sim.c new file mode 100644 index 000000000..a0f6596b0 --- /dev/null +++ b/src/tss2-tcti/tcti-start-sim.c @@ -0,0 +1,902 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2024, Infineon Technologies AG + * All rights reserved. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" // IWYU pragma: keep +#endif + +#include // for closedir, dirent, opendir, readdir, DIR +#include // for errno +#include // for PRIu16, PRIxPTR, PRIdMAX +#include // for PATH_MAX +#include // for kill, SIGTERM, SIGHUP, SIGKILL +#include // for false, true, bool +#include // for snprintf, asprintf +#include // for NULL, free, EXIT_FAILURE, EXIT_SUCCESS +#include // for strerror, strcmp, strdup, strlen, strsep +#include // for prctl, PR_SET_PDEATHSIG +#include // for chmod, S_IRGRP, S_IROTH, S_IRUSR, S_IWUSR +#include // for gettimeofday, timeval +#include // for size_t, pid_t, ssize_t +#include // for waitpid, WCOREDUMP, WNOHANG +#include // for usleep, chdir, execvp, fork, getpid + +#include "tcti-common.h" // for TSS2_TCTI_COMMON_CONTEXT, TCTI_STATE_TR... +#include "tss2_common.h" // for TSS2_RC_SUCCESS, TSS2_RC, TSS2_TCTI_RC_... +#include "tss2_tcti.h" // for TSS2_TCTI_CONTEXT, TSS2_TCTI_INFO, TSS2... +#include "tss2_tpm2_types.h" // for TPM2_RC_SUCCESS +#include "util/aux_util.h" // for ARRAY_LEN + +#define LOGMODULE tcti +#include "tcti-start-sim.h" +#include "tss2_tctildr.h" // for Tss2_TctiLdr_Finalize, Tss2_TctiLdr_Ini... +#include "util/log.h" // for LOG_ERROR, LOG_TRACE, LOG_DEBUG, LOG_WA... + + +#define PORT_MIN 1024 +#define PORT_MAX 65534 + + +static int get_mssim_command(char *command, size_t command_len, const char *workdir, uint16_t port) { + (void) workdir; + + int ret = snprintf(command, command_len, "tpm_server -port %" PRIu16, port); + if (ret < 0) { + LOG_ERROR("snprintf failed."); + return EXIT_FAILURE; + } + if (ret == sizeof(command)) { + LOG_ERROR("snprintf failed: output truncated."); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int get_swtpm_command(char *command, size_t command_len, const char *workdir, uint16_t port) { + int ret = snprintf(command, command_len, "swtpm socket --tpm2 -p %" PRIu16 " --ctrl type=tcp,port=%" PRIu16 " --log fd=1,level=5 --flags not-need-init --tpmstate dir=%s --locality allow-set-locality", port, port + 1, workdir); + if (ret < 0) { + LOG_ERROR("snprintf failed."); + return EXIT_FAILURE; + } + if (ret == sizeof(command)) { + LOG_ERROR("snprintf failed: output truncated."); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* Encapsulates all the const data we have to know about the different simulator variants */ +static const tcti_sim_variant tcti_sim_variants[] = { + { + .name = "mssim", + .name_len = sizeof(tcti_sim_variant[0]) - 1, + .get_command_fn = get_mssim_command, + .num_ports = 4, + }, + { + .name = "swtpm", + .name_len = sizeof(tcti_sim_variant[1]) - 1, + .get_command_fn = get_swtpm_command, + .num_ports = 2, + }, +}; + +/* + * Split string into array of words. + * Allocates memory which must be freed using split_string_free(). + * Adds an additional NULL at the end. + */ +static int split_string_alloc(char **dest[], size_t *dest_len, const char *src, const char delimiter) { + char *src_cpy; + char *token; + const char delimiter_str[] = { delimiter, '\0' }; + + /* Get number of elements, start counting at one because we always have one element more than delimiters */ + *dest_len = 1; + for (size_t i = 0; i < strlen(src); i++) { + if (src[i] == delimiter) { + (*dest_len)++; + } + } + + /* Add one since an additional element NULL is added */ + (*dest_len)++; + + /* Allocate array for string elements */ + *dest = malloc(sizeof(char *) * (*dest_len)); + if (dest == NULL) { + return EXIT_FAILURE; + } + + /* Copy input since strsep() mutates the string */ + src_cpy = strdup(src); + if (src_cpy == NULL) { + free(dest); + return EXIT_FAILURE; + } + + /* Jump to each string element, replacing the delimiters with '\0' */ + *dest_len = 0; + while ((token = strsep(&src_cpy, delimiter_str))) { + (*dest)[(*dest_len)++] = token; + } + + /* Add last element NULL */ + (*dest)[(*dest_len)++] = NULL; + + return EXIT_SUCCESS; +} + +static void split_string_free(char *dest[]) { + /* free memory from strdup() */ + free(dest[0]); + /* free array of elements */ + free(dest); +} + +static int simulator_make_workdir(char *tempdir_template) { + int ret; + char *tempdir; + + /* create temporary directory */ + tempdir = mkdtemp(tempdir_template); + if (tempdir == NULL) { + LOG_ERROR("mkdtemp failed for %s: %s", tempdir_template, strerror(errno)); + return EXIT_FAILURE; + } + ret = chmod(tempdir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret != 0) { + LOG_ERROR("chmod failed for %s: %s", tempdir, strerror(errno)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int simulator_execute_blocking(pid_t parent_pid, const char *workdir, char * const argv[]) { + int ret; + + LOG_DEBUG("TPM simulator process: changing directory: %s", workdir); + ret = chdir(workdir); + if (ret != 0) { + LOG_ERROR("chmod failed for %s: %s", workdir, strerror(errno)); + return EXIT_FAILURE; + } + + /* ask kernel to kill this child process when parent dies */ + ret = prctl(PR_SET_PDEATHSIG, SIGHUP); + if (ret == -1) { + LOG_ERROR("TPM simulator process error: prctl failed: %s", strerror(errno)); + return EXIT_FAILURE; + } + + /* double-check that parent did not die before we could set PR_SET_PDEATHSIG */ + if (getppid() != parent_pid) { + LOG_ERROR("TPM simulator process error: parent died early."); + return EXIT_FAILURE; + } + + ret = execvp(argv[0], argv); + if (ret == -1) { + LOG_ERROR("TPM simulator process error: execvp(%s) failed: %s", argv[0], strerror(errno)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int simulator_get_num_sockets(pid_t pid) { + int ret; + char fd_dir_path[PATH_MAX]; + DIR *dir; + struct dirent *entry; + char fd_link_path[PATH_MAX]; + char fd_dest_path[PATH_MAX]; + ssize_t len; + const char *socket_prefix = "socket:["; + size_t socket_prefix_strlen = strlen(socket_prefix); + long inode; + uint16_t num_sockets = 0; + + LOG_TRACE("Determining number of sockets"); + + ret = snprintf(fd_dir_path, sizeof(fd_dir_path), "/proc/%" PRIdMAX "/fd", (intmax_t) pid); + if (ret < 0) { + LOG_ERROR("snprintf failed."); + return -1; + } + if (ret == sizeof(fd_dir_path)) { + LOG_ERROR("snprintf failed: output truncated."); + return -1; + } + + dir = opendir(fd_dir_path); + if (dir == NULL) { + LOG_ERROR("opendir(%s) failed: %s", fd_dir_path, strerror(errno)); + return -1; + } + + /* iterate over /proc//fd/... */ + while (1) { + errno = 0; + entry = readdir(dir); + if (entry == NULL) { + if (errno != 0) { + LOG_ERROR("readdir failed: %s", strerror(errno)); + return -1; + } + + /* no more directory entries */ + break; + } + + /* skip entries . and .. */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + /* construct file name for file in /proc//fd/ */ + ret = snprintf(fd_link_path, sizeof(fd_link_path), "%s/%s", fd_dir_path, entry->d_name); + if (ret < 0) { + LOG_ERROR("snprintf failed"); + return -1; + } + if (ret == sizeof(fd_dir_path)) { + LOG_ERROR("snprintf failed: output truncated."); + return -1; + } + + /* file is a symlink, read destination path (might be "socket:[]") */ + len = readlink(fd_link_path, fd_dest_path, sizeof(fd_dest_path) - 1); + if (len == -1) { + LOG_ERROR("readlink failed for %s: %s", fd_link_path, strerror(errno)); + return -1; + } + /* readlink does not null-terminate fd_dest_path, fix that */ + fd_dest_path[len++] = '\0'; + + /* check if symlink points to socket (i.e. starts with socket_prefix), if not skip */ + if (strncmp(fd_dest_path, socket_prefix, socket_prefix_strlen) != 0) { + continue; + } + + errno = 0; + inode = strtol(fd_dest_path + socket_prefix_strlen, NULL, 10); + if (errno != 0) { + LOG_ERROR("strtol failed: %s", strerror(errno)); + return -1; + } + + LOG_TRACE("Found socket (inode=%ld)", inode); + num_sockets++; + } + + ret = closedir(dir); + if (ret != 0) { + LOG_ERROR("closedir failed: %s", strerror(errno)); + return -1; + } + + LOG_TRACE("Number of sockets: %d", num_sockets); + + return num_sockets; +} + +static int simulator_check_alive(pid_t child_pid) { + /* we are the parent, child is pid */ + + int status; + int r = waitpid(child_pid, &status, WNOHANG); + if (r < 0) { + LOG_ERROR("waitpid failed. Did TPM simulator with PID %d die?", child_pid); + return EXIT_FAILURE; + } else if (r == child_pid && WIFEXITED(status)) { + LOG_DEBUG("TPM simulator exited: %d", WEXITSTATUS(status)); + return WEXITSTATUS(status); + } else if (r == child_pid && WIFSIGNALED(status)) { + LOG_DEBUG("TPM simulator terminated by signal: %d", WTERMSIG(status)); +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + LOG_WARNING("TPM simulator: Core dumped."); + } + return EXIT_FAILURE; +#endif + /* + * Only relevant for waitpid(pid, WUNTRACED) for resuming the parent after child was stopped via SIGSTOP + * } else if (r == pid && WIFSTOPPED(status)) { + * printf("Stopped by signal: %d\n", WSTOPSIG(status)); + * } else if (r == pid && WIFCONTINUED(status)) { + * printf("continued\n"); + */ + } else { + LOG_TRACE("TPM simulator with PID %d is still alive.", child_pid); + } + + return EXIT_SUCCESS; +} + + +static int simulator_start_and_wait_for_ports(pid_t *simulator_pid, const tcti_sim_variant *variant, uint16_t port) { + int r; + int num_sockets; + char command[PATH_MAX]; + char workdir[] = "/tmp/tcti-start-sim-XXXXXX"; + char **argv; + size_t argc; + + /* create simulator workdir to a temporary directory to deal with state files */ + r = simulator_make_workdir(workdir); + if (r != EXIT_SUCCESS) { + return r; + } + + /* get command to execute simulator as single string with spaces */ + r = variant->get_command_fn(command, sizeof(command), workdir, port); + if (r != EXIT_SUCCESS) { + return r; + } + LOG_DEBUG("Starting TPM simulator: %s", command); + + /* split command into argv array */ + r = split_string_alloc(&argv, &argc, command, ' '); + if (r != EXIT_SUCCESS) { + return r; + } + + pid_t parent_pid = getpid(); + *simulator_pid = fork(); + if (*simulator_pid == -1) { + LOG_ERROR("fork failed: %s", strerror(errno)); + return EXIT_FAILURE; + } + if (*simulator_pid == 0) { + /* we are the child and will be the simulator process */ + + r = simulator_execute_blocking(parent_pid, workdir, argv); + /* no error handling, we only reach here on error */ + + split_string_free(argv); + + return r; + } + + /* we are the parent */ + LOG_TRACE("TPM simulator has PID %d", *simulator_pid); + + /* busy wait until ports are open */ + while (1) { + r = simulator_check_alive(*simulator_pid); + if (r != EXIT_SUCCESS) { + return -1; + } + + num_sockets = simulator_get_num_sockets(*simulator_pid); + if (num_sockets < 0) { + return -1; + } + if (num_sockets >= variant->num_ports) { + break; + } + + /* wait 10ms */ + r = usleep(10000); + if (r < 0) { + LOG_ERROR("usleep failed: %s", strerror(errno)); + } + } + + return EXIT_SUCCESS; +} + +static int simulator_kill(pid_t simulator_pid) { + int ret; + int status; + int signals[] = { SIGTERM, SIGKILL }; + const char *signal_names[] = { str(SIGTERM), str(SIGKILL) }; + + /* try to kill simulator; ask nicely first (SIGTERM), then with more insistence (SIGKILL) */ + for (size_t i = 0; i < ARRAY_LEN(signals); i++) { + LOG_TRACE("Sending kill(%s=%d) to TPM simulator", signal_names[i], signals[i]); + + ret = kill(simulator_pid, SIGTERM); + if (ret == -1) { + LOG_WARNING("kill(%s=%d) of TPM simulator failed: %s", signal_names[i], signals[i], strerror(errno)); + /* sleep and try again in next iteration */ + } + + /* + * wait for TPM simulator to actually terminate. + * we will escalate to next signal after 20*10ms = 200ms + */ + for (int j = 0; j < 20; j++) { + ret = waitpid(simulator_pid, &status, WNOHANG); + if (ret < 0) { + LOG_ERROR("waitpid failed. Did TPM simulator with PID %d die?", simulator_pid); + /* sleep and try again in next iteration */ + } else if (ret == simulator_pid && WIFEXITED(status)) { + LOG_DEBUG("TPM simulator exited on its own: %d", WEXITSTATUS(status)); + return EXIT_SUCCESS; + } else if (ret == simulator_pid && WIFSIGNALED(status)) { + LOG_DEBUG("TPM simulator terminated by signal: %d", WTERMSIG(status)); + #ifdef WCOREDUMP + if (WCOREDUMP(status)) { + LOG_WARNING("TPM simulator: Core dumped."); + } + #endif + return EXIT_SUCCESS; + /* + * Only relevant for waitpid(pid, WUNTRACED) for resuming the parent after child was stopped via SIGSTOP + * } else if (r == pid && WIFSTOPPED(status)) { + * printf("Stopped by signal: %d\n", WSTOPSIG(status)); + * } else if (r == pid && WIFCONTINUED(status)) { + * printf("continued\n"); + */ + } else { + LOG_TRACE("TPM simulator with PID %d is still alive.", simulator_pid); + } + + usleep(10000); + } + + usleep(50000); + } + + /* + * Even if we could not kill the child, it will be killed via PR_SET_PDEATHSIG + * when the parent dies at the latest. + */ + + return EXIT_FAILURE; +} + +static int get_random_port() { + static bool is_seeded = false; + if (!is_seeded) { + /* seeding with seconds is not enough */ + struct timeval time; + gettimeofday(&time, NULL); + srand(time.tv_usec * time.tv_sec); + is_seeded = true; + } + return PORT_MIN + rand() % (PORT_MAX - PORT_MIN); +} + +/* + * Search for first occurance of known TPM simulator tctis (mssim, swtpm) in + * conf string and choose simulator variant based on that. If a port is passed + * to mssim/swtpm in its conf, parse that, too. + * + * If no known tcti could be found, return error. + * If no port could be found, a random one is used. + * + */ +static TSS2_RC +tcti_start_sim_conf_parse_alloc(const char *conf, const tcti_sim_variant **variant, uint16_t *port, char **new_conf) +{ + int ret; + TSS2_RC rc; + char *conf_cpy; + char *element; + bool is_simulator_tcti_name; + bool was_simulator_tcti_conf_parsed = false; + const char *port_prefix = "port="; + size_t port_prefix_strlen = strlen(port_prefix); + const char *port_substring; + + *new_conf = NULL; + + /* + * Bear with me here. + * + * This is complex for two reasons. Firstly, we need to do string parsing in + * C. I'm yearning for Rust right now, but oh well - we'll get there + * eventually. + * + * Secondly, we need to be able to deal with various config strings - and if + * no port was given even manipulate the config string. + * + * Allowed: + * - mssim:host=localhost,port=2321 + * - swtpm:host=localhost,port=2321 + * - pcap:mssim:host=localhost + * - pcap:mssim + * - mssim + * + * Not allowed + * - "" + * - device + * - mssim:foo:bar + * + * Rules: + * - A valid config string can contain 0..n colons, thay split the string + * into elements + * - We search for the first element which starts with a known tcti + * simulator tcti name ("mssim", "swtpm") + * - This simulator tcti name element can be followed by an optional + * simulator tcti config element (e.g. "host=localhost,port=2321") + * - After this, no further element is allowed + * - If there is no simulator tcti config element, a random port is chosen + * and a config is appended (e.g. ":port=12345") + * - If a there is a simulator tcti config element which does not specify a + * port, a random port is chosen and a key-value-pair is appended (e.g. + * ",port=12344") + * + * Most of this should be moved to a common config string + * parsing/manipulation library for all tctis, preferably in Rust. + */ + + /* Copy input since strsep() mutates the string */ + conf_cpy = strdup(conf); + if (conf_cpy == NULL) { + return TSS2_TCTI_RC_MEMORY; + } + + *variant = NULL; + *port = 0; + is_simulator_tcti_name = false; + was_simulator_tcti_conf_parsed = false; + + /* For each element (separated by ':' which will be replaced with '\0') */ + while ((element = strsep(&conf_cpy, ":"))) { + is_simulator_tcti_name = false; + + /* For each known simulator tcti name */ + for (size_t i = 0; i < ARRAY_LEN(tcti_sim_variants); i++) { + if (strcmp(element, tcti_sim_variants[i].name) == 0) { + is_simulator_tcti_name = true; + *variant = &tcti_sim_variants[i]; + break; + } + } + + if (is_simulator_tcti_name) { + is_simulator_tcti_name = false; + continue; + } + + if (*variant == NULL) { + /* simulator tcti name not found, continue searching */ + continue; + } + + /* simulator tcti name is found */ + + if (was_simulator_tcti_conf_parsed) { + /* simulator tcti conf is also found, that means this element is an + * excess element + */ + rc = TSS2_TCTI_RC_BAD_VALUE; + goto cleanup; + } + + /* this element is the simulator tcti conf, parse port if given */ + /* parse port if given (first occurance of "port=")*/ + port_substring = strstr(element, port_prefix); + if (port_substring == NULL) { + /* no port found */ + *port = 0; + was_simulator_tcti_conf_parsed = true; + continue; + } + + errno = 0; + *port = strtoul(port_substring + port_prefix_strlen, NULL, 10); + if (errno != 0) { + LOG_ERROR("strtoul failed: %s", strerror(errno)); + rc = TSS2_TCTI_RC_GENERAL_FAILURE; + goto cleanup; + } + LOG_TRACE("Found TPM simulator port: %" PRIu16, *port); + + was_simulator_tcti_conf_parsed = true; + } + + if (*variant == NULL) { + LOG_ERROR("Unknown child TCTI: %s", conf); + rc = TSS2_TCTI_RC_BAD_VALUE; + goto cleanup; + } + + if (*port == 0) { + *port = get_random_port(); + + /* port was chosen randomly, add key-value-pair to child conf */ + if (was_simulator_tcti_conf_parsed) { + ret = asprintf(new_conf, "%s,port=%" PRIu16, conf, *port); + } else { + ret = asprintf(new_conf, "%s:port=%" PRIu16, conf, *port); + } + if (ret == -1) { + LOG_ERROR("asprintf failed."); + rc = TSS2_TCTI_RC_MEMORY; + goto cleanup; + } + } else { + /* port was specified, no changes to tcti_child_conf */ + *new_conf = strdup(conf); + if (*new_conf == NULL) { + LOG_ERROR("strdup failed."); + rc = TSS2_TCTI_RC_MEMORY; + goto cleanup; + } + } + + rc = TSS2_RC_SUCCESS; + +cleanup: + free(conf_cpy); + + return rc; +} + +/* + * This function wraps the "up-cast" of the opaque TCTI context type to the + * type for the pcap TCTI context. The only safeguard we have to ensure this + * operation is possible is the magic number in the pcap TCTI context. + * If passed a NULL context, or the magic number check fails, this function + * will return NULL. + */ +static TSS2_TCTI_START_SIM_CONTEXT* +tcti_start_sim_context_cast (TSS2_TCTI_CONTEXT *tcti_ctx) +{ + if (tcti_ctx != NULL && TSS2_TCTI_MAGIC (tcti_ctx) == TCTI_START_SIM_MAGIC) { + return (TSS2_TCTI_START_SIM_CONTEXT*)tcti_ctx; + } + return NULL; +} + +/* + * This function down-casts the pcap TCTI context to the common context + * defined in the tcti-common module. + */ +static TSS2_TCTI_COMMON_CONTEXT* +tcti_start_sim_down_cast (TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim) +{ + if (tcti_start_sim == NULL) { + return NULL; + } + return &tcti_start_sim->common; +} + +TSS2_RC +tcti_start_sim_transmit ( + TSS2_TCTI_CONTEXT *tcti_ctx, + size_t size, + const uint8_t *cmd_buf) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tcti_ctx); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + TSS2_RC rc; + + if (tcti_start_sim == NULL) { + return TSS2_TCTI_RC_BAD_CONTEXT; + } + rc = tcti_common_transmit_checks (tcti_common, cmd_buf, TCTI_START_SIM_MAGIC); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + LOGBLOB_DEBUG (cmd_buf, size, "sending %zu byte command buffer:", size); + + rc = Tss2_Tcti_Transmit (tcti_start_sim->tcti_child, size, cmd_buf); + if (rc != TSS2_RC_SUCCESS) { + LOG_ERROR ("Failed calling TCTI transmit of child TCTI module"); + return rc; + } + + tcti_common->state = TCTI_STATE_RECEIVE; + return TSS2_RC_SUCCESS; +} + +TSS2_RC +tcti_start_sim_receive ( + TSS2_TCTI_CONTEXT *tctiContext, + size_t *response_size, + unsigned char *response_buffer, + int32_t timeout) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tctiContext); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + TSS2_RC rc; + + if (tcti_start_sim == NULL) { + return TSS2_TCTI_RC_BAD_CONTEXT; + } + rc = tcti_common_receive_checks (tcti_common, response_size, TCTI_START_SIM_MAGIC); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + rc = Tss2_Tcti_Receive (tcti_start_sim->tcti_child, + response_size, response_buffer, + timeout); + if (rc != TPM2_RC_SUCCESS) { + return rc; + } + + /* partial read */ + if (response_buffer == NULL) { + return rc; + } + + LOGBLOB_DEBUG (response_buffer, *response_size, "Response Received"); + + tcti_common->state = TCTI_STATE_TRANSMIT; + return rc; +} + +TSS2_RC +tcti_start_sim_cancel ( + TSS2_TCTI_CONTEXT *tctiContext) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tctiContext); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + TSS2_RC rc; + + if (tcti_start_sim == NULL) { + return TSS2_TCTI_RC_BAD_CONTEXT; + } + rc = tcti_common_cancel_checks (tcti_common, TCTI_START_SIM_MAGIC); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + LOG_WARNING ("Logging Tcti_Cancel to a PCAP file is not implemented"); + + rc = Tss2_Tcti_Cancel (tcti_start_sim->tcti_child); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + tcti_common->state = TCTI_STATE_TRANSMIT; + return rc; +} + +TSS2_RC +tcti_start_sim_set_locality ( + TSS2_TCTI_CONTEXT *tctiContext, + uint8_t locality) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tctiContext); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + TSS2_RC rc; + + if (tcti_start_sim == NULL) { + return TSS2_TCTI_RC_BAD_CONTEXT; + } + + rc = tcti_common_set_locality_checks (tcti_common, TCTI_START_SIM_MAGIC); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + rc = Tss2_Tcti_SetLocality (tcti_start_sim->tcti_child, locality); + if (rc != TSS2_RC_SUCCESS) { + return rc; + } + + tcti_common->locality = locality; + return rc; +} + +TSS2_RC +tcti_start_sim_get_poll_handles ( + TSS2_TCTI_CONTEXT *tctiContext, + TSS2_TCTI_POLL_HANDLE *handles, + size_t *num_handles) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tctiContext); + + if (tcti_start_sim == NULL) { + return TSS2_TCTI_RC_BAD_CONTEXT; + } + + return Tss2_Tcti_GetPollHandles (tcti_start_sim->tcti_child, handles, + num_handles); +} + +void +tcti_start_sim_finalize ( + TSS2_TCTI_CONTEXT *tctiContext) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = tcti_start_sim_context_cast (tctiContext); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + + if (tcti_start_sim == NULL) { + return; + } + + Tss2_TctiLdr_Finalize (&tcti_start_sim->tcti_child); + + free(tcti_start_sim->tcti_child_name_conf); + + /* we cannot recover from a failed kill. ignore return code */ + simulator_kill(tcti_start_sim->simulator_pid); + + tcti_common->state = TCTI_STATE_FINAL; +} + +/* + * This is an implementation of the standard TCTI initialization function for + * this module. + */ +TSS2_RC +Tss2_Tcti_Start_Sim_Init ( + TSS2_TCTI_CONTEXT *tctiContext, + size_t *size, + const char *conf) +{ + TSS2_TCTI_START_SIM_CONTEXT *tcti_start_sim = (TSS2_TCTI_START_SIM_CONTEXT*) tctiContext; + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_start_sim_down_cast (tcti_start_sim); + TSS2_RC rc = TSS2_RC_SUCCESS; + int ret; + + if (tctiContext == NULL && size == NULL) { + return TSS2_TCTI_RC_BAD_VALUE; + } else if (tctiContext == NULL) { + *size = sizeof (TSS2_TCTI_START_SIM_CONTEXT); + return TSS2_RC_SUCCESS; + } + + LOG_TRACE ("tctiContext: 0x%" PRIxPTR ", size: 0x%" PRIxPTR ", conf: %s", + (uintptr_t)tctiContext, (uintptr_t)size, conf); + + rc = tcti_start_sim_conf_parse_alloc(conf, &tcti_start_sim->variant, &tcti_start_sim->port, &tcti_start_sim->tcti_child_name_conf); + if (rc != TSS2_RC_SUCCESS) { + return TSS2_TCTI_RC_BAD_VALUE; + } + + ret = simulator_start_and_wait_for_ports(&tcti_start_sim->simulator_pid, tcti_start_sim->variant, tcti_start_sim->port); + if (ret != EXIT_SUCCESS) { + rc = TSS2_TCTI_RC_IO_ERROR; + goto cleanup_tcti_child_name_conf; + } + + rc = Tss2_TctiLdr_Initialize (tcti_start_sim->tcti_child_name_conf, &tcti_start_sim->tcti_child); + if (rc != TSS2_RC_SUCCESS) { + LOG_ERROR ("Error loading TCTI: %s", conf); + goto cleanup_tcti_child; + } + + TSS2_TCTI_MAGIC (tcti_common) = TCTI_START_SIM_MAGIC; + TSS2_TCTI_VERSION (tcti_common) = TCTI_VERSION; + TSS2_TCTI_TRANSMIT (tcti_common) = tcti_start_sim_transmit; + TSS2_TCTI_RECEIVE (tcti_common) = tcti_start_sim_receive; + TSS2_TCTI_FINALIZE (tcti_common) = tcti_start_sim_finalize; + TSS2_TCTI_CANCEL (tcti_common) = tcti_start_sim_cancel; + TSS2_TCTI_GET_POLL_HANDLES (tcti_common) = tcti_start_sim_get_poll_handles; + TSS2_TCTI_SET_LOCALITY (tcti_common) = tcti_start_sim_set_locality; + TSS2_TCTI_MAKE_STICKY (tcti_common) = tcti_make_sticky_not_implemented; + tcti_common->state = TCTI_STATE_TRANSMIT; + tcti_common->locality = 3; + memset (&tcti_common->header, 0, sizeof (tcti_common->header)); + + return TSS2_RC_SUCCESS; + +cleanup_tcti_child_name_conf: + free(tcti_start_sim->tcti_child_name_conf); +cleanup_tcti_child: + /* we cannot recover from a failed kill. ignore return code */ + simulator_kill(tcti_start_sim->simulator_pid); + return rc; +} + +/* public info structure */ +const TSS2_TCTI_INFO tss2_tcti_info = { + .version = TCTI_VERSION, + .name = "tcti-start-sim", + .description = "TCTI module for starting a TPM simulator process.", + .config_help = "The child tcti module and its config string: :", + .init = Tss2_Tcti_Start_Sim_Init, +}; + +const TSS2_TCTI_INFO* +Tss2_Tcti_Info (void) +{ + return &tss2_tcti_info; +} diff --git a/src/tss2-tcti/tcti-start-sim.h b/src/tss2-tcti/tcti-start-sim.h new file mode 100644 index 000000000..8104fc89d --- /dev/null +++ b/src/tss2-tcti/tcti-start-sim.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2024, Infineon Technologies AG + * All rights reserved. + */ + +#ifndef TCTI_START_SIM_H +#define TCTI_START_SIM_H + +#include // for uint16_t +#include // for size_t, pid_t + +#include "tcti-common.h" // for TSS2_TCTI_COMMON_CONTEXT +#include "tss2_tcti.h" // for TSS2_TCTI_CONTEXT + +#define TCTI_START_SIM_MAGIC 0x535441525453494dULL + +typedef int get_command_fn(char *command, size_t command_len, const char *workdir, uint16_t port); + +typedef struct { + const char *name; + const size_t name_len; + get_command_fn *get_command_fn; + uint16_t num_ports; +} tcti_sim_variant; + +typedef struct { + TSS2_TCTI_COMMON_CONTEXT common; + TSS2_TCTI_CONTEXT *tcti_child; + char *tcti_child_name_conf; + pid_t simulator_pid; + const tcti_sim_variant *variant; + uint16_t port; +} TSS2_TCTI_START_SIM_CONTEXT; + +#endif /* TCTI_START_SIM_H */