| /* |
| * mainloop.c -- The TPM Emulator's main processing loop |
| * |
| * (c) Copyright IBM Corporation 2014, 2015, 2016 |
| * |
| * Author: Stefan Berger <stefanb@us.ibm.com> |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * Neither the names of the IBM Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* mainLoop() is the main server loop. |
| |
| It reads a TPM request, processes the ordinal, and writes the response |
| */ |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <poll.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <sys/socket.h> |
| |
| #include <libtpms/tpm_error.h> |
| #include <libtpms/tpm_library.h> |
| #include <libtpms/tpm_memory.h> |
| |
| #include "swtpm_debug.h" |
| #include "swtpm_io.h" |
| #include "tpmlib.h" |
| #include "locality.h" |
| #include "logging.h" |
| #include "ctrlchannel.h" |
| #include "mainloop.h" |
| #include "utils.h" |
| #include "sys_dependencies.h" |
| #include "compiler_dependencies.h" |
| #include "swtpm_utils.h" |
| #include "swtpm_nvstore.h" |
| |
| /* local variables */ |
| static TPM_MODIFIER_INDICATOR locality; |
| bool tpm_running = false; |
| |
| bool mainloop_terminate; |
| |
| TPM_RESULT |
| mainloop_cb_get_locality(TPM_MODIFIER_INDICATOR *loc, |
| uint32_t tpmnum SWTPM_ATTR_UNUSED) |
| { |
| *loc = locality; |
| |
| return TPM_SUCCESS; |
| } |
| |
| /* ensure that the storage is locked; returns false in case of failure */ |
| bool mainloop_ensure_locked_storage(struct mainLoopParams *mlp) |
| { |
| TPM_RESULT res; |
| |
| if (mlp->storage_locked) |
| return true; |
| |
| /* if NVRAM hasn't been initialized yet locking may need to be retried */ |
| res = SWTPM_NVRAM_Lock_Storage(mlp->locking_retries); |
| if (res == TPM_RETRY) |
| return true; |
| if (res != TPM_SUCCESS) |
| return false; |
| |
| mlp->locking_retries = 0; |
| mlp->storage_locked = true; |
| mlp->incoming_migration = false; |
| |
| return true; |
| } |
| |
| void mainloop_unlock_nvram(struct mainLoopParams *mlp, |
| unsigned int locking_retries) |
| { |
| SWTPM_NVRAM_Unlock(); |
| |
| mlp->storage_locked = false; |
| mlp->locking_retries = locking_retries; |
| } |
| |
| int mainLoop(struct mainLoopParams *mlp, |
| int notify_fd) |
| { |
| TPM_RESULT rc = 0; |
| TPM_CONNECTION_FD connection_fd; /* file descriptor for read/write */ |
| unsigned char *command = NULL; /* command buffer */ |
| uint32_t command_length; /* actual length of command bytes */ |
| uint32_t max_command_length; /* command buffer size */ |
| off_t cmd_offset; |
| /* The response buffer is reused for each command. Thus it can grow but never shrink */ |
| unsigned char *rbuffer = NULL; /* actual response bytes */ |
| uint32_t rlength = 0; /* bytes in response buffer */ |
| uint32_t rTotal = 0; /* total allocated bytes */ |
| int ctrlfd; |
| int ctrlclntfd; |
| int sockfd; |
| int ready; |
| struct iovec iov[3]; |
| uint32_t ack = htobe32(0); |
| struct tpm2_resp_prefix respprefix; |
| uint32_t lastCommand; |
| |
| /* poolfd[] indexes */ |
| enum { |
| DATA_CLIENT_FD = 0, |
| NOTIFY_FD, |
| CTRL_SERVER_FD, |
| CTRL_CLIENT_FD, |
| DATA_SERVER_FD |
| }; |
| |
| TPM_DEBUG("mainLoop:\n"); |
| |
| max_command_length = tpmlib_get_tpm_property(TPMPROP_TPM_BUFFER_MAX) + |
| sizeof(struct tpm2_send_command_prefix); |
| |
| command = malloc(max_command_length); |
| if (!command) { |
| logprintf(STDERR_FILENO, "Could not allocate %u bytes for buffer.\n", |
| max_command_length); |
| return TPM_FAIL; |
| } |
| |
| /* header and trailer that we may send by setting iov_len */ |
| iov[0].iov_base = &respprefix; |
| iov[0].iov_len = 0; |
| iov[2].iov_base = &ack; |
| iov[2].iov_len = 0; |
| |
| connection_fd.fd = -1; |
| ctrlfd = ctrlchannel_get_fd(mlp->cc); |
| ctrlclntfd = ctrlchannel_get_client_fd(mlp->cc); |
| |
| sockfd = SWTPM_IO_GetSocketFD(); |
| |
| if (mlp->startupType != _TPM_ST_NONE) { |
| command_length = tpmlib_create_startup_cmd( |
| mlp->startupType, |
| mlp->tpmversion, |
| command, max_command_length); |
| if (command_length > 0) { |
| mlp->lastCommand = tpmlib_get_cmd_ordinal(command, command_length); |
| rc = TPMLIB_Process(&rbuffer, &rlength, &rTotal, |
| command, command_length); |
| } |
| |
| if (rc || command_length == 0) { |
| mainloop_terminate = true; |
| if (rc) |
| logprintf(STDERR_FILENO, "Could not send Startup: 0x%x\n", rc); |
| } |
| } |
| |
| while (!mainloop_terminate) { |
| |
| while (rc == 0) { |
| if (mlp->flags & MAIN_LOOP_FLAG_USE_FD) |
| connection_fd.fd = mlp->fd; |
| |
| struct pollfd pollfds[] = { |
| [DATA_CLIENT_FD] = { |
| .fd = connection_fd.fd, |
| .events = POLLIN | POLLHUP, |
| .revents = 0, |
| }, |
| [NOTIFY_FD] = { |
| .fd = notify_fd, |
| .events = POLLIN, |
| .revents = 0, |
| }, |
| [CTRL_SERVER_FD] = { |
| .fd = -1, |
| .events = POLLIN, |
| .revents = 0, |
| }, |
| [CTRL_CLIENT_FD] = { |
| .fd = ctrlclntfd, |
| .events = POLLIN | POLLHUP, |
| .revents = 0, |
| }, |
| [DATA_SERVER_FD] = { |
| /* listen socket for accepting clients */ |
| .fd = -1, |
| .events = POLLIN, |
| .revents = 0, |
| } |
| }; |
| |
| /* only listend for clients if we don't have one */ |
| if (connection_fd.fd < 0) |
| pollfds[DATA_SERVER_FD].fd = sockfd; |
| if (ctrlclntfd < 0) |
| pollfds[CTRL_SERVER_FD].fd = ctrlfd; |
| |
| ready = poll(pollfds, 5, -1); |
| if (ready < 0 && errno == EINTR) |
| continue; |
| |
| if (ready < 0 || |
| (pollfds[NOTIFY_FD].revents & POLLIN) != 0) { |
| SWTPM_IO_Disconnect(&connection_fd); |
| break; |
| } |
| |
| if (pollfds[DATA_CLIENT_FD].revents & (POLLHUP | POLLERR)) { |
| logprintf(STDERR_FILENO, "Data client disconnected\n"); |
| mlp->fd = -1; |
| /* chardev and unixio get this signal, not tcp */ |
| if (mlp->flags & MAIN_LOOP_FLAG_END_ON_HUP) { |
| /* only the chardev terminates here */ |
| mainloop_terminate = true; |
| break; |
| } |
| } |
| |
| if (pollfds[DATA_SERVER_FD].revents & POLLIN) |
| connection_fd.fd = accept(pollfds[DATA_SERVER_FD].fd, NULL, 0); |
| |
| if (pollfds[CTRL_SERVER_FD].revents & POLLIN) |
| ctrlclntfd = accept(ctrlfd, NULL, 0); |
| |
| if (pollfds[CTRL_CLIENT_FD].revents & POLLIN) { |
| ctrlclntfd = ctrlchannel_process_fd(ctrlclntfd, |
| &mainloop_terminate, |
| &locality, &tpm_running, |
| mlp); |
| if (ctrlclntfd < 0 && |
| mlp->flags & MAIN_LOOP_FLAG_CTRL_END_ON_HUP) |
| mainloop_terminate = true; |
| |
| if (mainloop_terminate) |
| break; |
| } |
| |
| if (pollfds[CTRL_CLIENT_FD].revents & POLLHUP) { |
| if (ctrlclntfd >= 0) |
| close(ctrlclntfd); |
| ctrlclntfd = -1; |
| /* unixio gets this signal, not tcp */ |
| if (mlp->flags & MAIN_LOOP_FLAG_CTRL_END_ON_HUP) { |
| mainloop_terminate = true; |
| break; |
| } |
| } |
| |
| if (!(pollfds[DATA_CLIENT_FD].revents & POLLIN)) |
| continue; |
| |
| /* before processing a command ensure that the storage is locked */ |
| if ((mainloop_terminate = !mainloop_ensure_locked_storage(mlp))) |
| break; |
| |
| /* Read the command. The number of bytes is determined by 'paramSize' in the stream */ |
| if (rc == 0) { |
| rc = SWTPM_IO_Read(&connection_fd, command, &command_length, |
| max_command_length); |
| if (rc != 0) { |
| /* connection broke */ |
| SWTPM_IO_Disconnect(&connection_fd); |
| } |
| } |
| |
| cmd_offset = 0; |
| /* Handle optional TCG Header in front of TPM 2 Command */ |
| if (rc == 0 && mlp->tpmversion == TPMLIB_TPM_VERSION_2) { |
| cmd_offset = tpmlib_handle_tcg_tpm2_cmd_header(command, |
| command_length, |
| &locality); |
| if (cmd_offset > 0) { |
| /* send header and trailer */ |
| iov[0].iov_len = sizeof(respprefix); |
| iov[2].iov_len = sizeof(ack); |
| } else { |
| iov[0].iov_len = 0; |
| iov[2].iov_len = 0; |
| } |
| } |
| |
| if (rc == 0) { |
| if (!tpm_running) { |
| tpmlib_write_fatal_error_response(&rbuffer, &rlength, |
| &rTotal, |
| mlp->tpmversion); |
| goto skip_process; |
| } |
| } |
| |
| if (rc == 0) { |
| lastCommand = |
| tpmlib_get_cmd_ordinal(&command[cmd_offset], |
| command_length - cmd_offset); |
| if (lastCommand != TPM_ORDINAL_NONE) |
| mlp->lastCommand = lastCommand; |
| } |
| |
| if (rc == 0) { |
| rlength = 0; /* clear the response buffer */ |
| rc = tpmlib_process(&rbuffer, |
| &rlength, |
| &rTotal, |
| &command[cmd_offset], |
| command_length - cmd_offset, |
| mlp->locality_flags, |
| &locality, |
| mlp->tpmversion); |
| if (rlength) |
| goto skip_process; |
| } |
| |
| if (rc == 0) { |
| rlength = 0; /* clear the response buffer */ |
| rc = TPMLIB_Process(&rbuffer, |
| &rlength, |
| &rTotal, |
| &command[cmd_offset], |
| command_length - cmd_offset); |
| } |
| |
| skip_process: |
| /* write the results */ |
| if (rc == 0) { |
| respprefix.size = htobe32(rlength); |
| iov[1].iov_base = rbuffer; |
| iov[1].iov_len = rlength; |
| |
| SWTPM_IO_Write(&connection_fd, iov, ARRAY_LEN(iov)); |
| } |
| |
| if (!(mlp->flags & MAIN_LOOP_FLAG_KEEP_CONNECTION)) { |
| SWTPM_IO_Disconnect(&connection_fd); |
| break; |
| } |
| } |
| |
| rc = 0; /* A fatal TPM_Process() error should cause the TPM to enter shutdown. IO errors |
| are outside the TPM, so the TPM does not shut down. The main loop should |
| continue to function.*/ |
| if (connection_fd.fd < 0 && mlp->flags & MAIN_LOOP_FLAG_TERMINATE) |
| break; |
| } |
| |
| if (tpm_running && !mlp->disable_auto_shutdown) |
| tpmlib_maybe_send_tpm2_shutdown(mlp->tpmversion, &mlp->lastCommand); |
| |
| free(rbuffer); |
| free(command); |
| |
| if (ctrlclntfd >= 0) |
| close(ctrlclntfd); |
| ctrlchannel_set_client_fd(mlp->cc, -1); |
| |
| if (connection_fd.fd >= 0 && !(mlp->flags & MAIN_LOOP_FLAG_USE_FD)) |
| close(connection_fd.fd); |
| |
| if (mlp->fd >= 0) { |
| close(mlp->fd); |
| mlp->fd = -1; |
| } |
| |
| return rc; |
| } |