blob: 846f02c03d535e24a9e1649484165204931295ae [file] [log] [blame]
/*
* 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 (mlp->fd >= 0) {
close(mlp->fd);
mlp->fd = -1;
}
return rc;
}