blob: 5977b63de681cd30aa762fdbcfbfcce09a809652 [file] [log] [blame]
/********************************************************************************/
/* */
/* CUSE TPM */
/* IBM Thomas J. Watson Research Center */
/* */
/* (c) Copyright IBM Corporation 2014-2015. */
/* */
/* 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. */
/********************************************************************************/
/*
* Authors:
* Eric Richter, erichte@us.ibm.com
* Stefan Berger, stefanb@us.ibm.com
* David Safford, safford@us.ibm.com
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include <errno.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <fuse/cuse_lowlevel.h>
#include <glib.h>
#include <libtpms/tpm_library.h>
#include <libtpms/tpm_tis.h>
#include <libtpms/tpm_error.h>
#include <libtpms/tpm_memory.h>
#include "swtpm.h"
#include "common.h"
#include "tpmstate.h"
#include "pidfile.h"
#include "locality.h"
#include "logging.h"
#include "tpm_ioctl.h"
#include "swtpm_nvstore.h"
#include "tpmlib.h"
#include "main.h"
#include "utils.h"
#include "threadpool.h"
#include "seccomp_profile.h"
#include "options.h"
#include "capabilities.h"
#include "swtpm_utils.h"
/* maximum size of request buffer */
#define TPM_REQ_MAX 4096
/* version of the TPM (1.2 or 2) */
static TPMLIB_TPMVersion tpmversion;
/* buffer containing the TPM request */
static unsigned char *ptm_request;
/* buffer containing the TPM response; protected by file_ops_lock and thread_busy_lock */
static unsigned char *ptm_response;
/* offset from where to read from; reset when ptm_response is set */
static size_t ptm_read_offset;
/* the sizes of the data in the buffers */
static uint32_t ptm_req_len, ptm_res_len, ptm_res_tot;
/* locality applied to TPM commands */
static TPM_MODIFIER_INDICATOR locality;
/* whether the TPM is running (TPM_Init was received); protected by file_ops_lock */
static bool tpm_running;
/* flags on how to handle locality */
static uint32_t locality_flags;
/* the fuse_session that we will signal an exit to to exit the prg. */
static struct fuse_session *ptm_fuse_session;
/* last command sent to the TPM */
static uint32_t g_lastCommand = TPM_ORDINAL_NONE;
/* TPM2_Shutdown() will NOT be sent by swtpm */
static bool g_disable_auto_shutdown = false;
/* whether to defer locking NVRAM storage due to incoming migration */
static bool g_incoming_migration;
/* whether NVRAM storage is currently locked */
static bool g_storage_locked;
/* whether to release the lock on outgoing migration */
static bool g_release_lock_outgoing;
/* how many times to retry locking; use for fallback after releasing
the lock on outgoing migration. */
static unsigned int g_locking_retries;
#define DEFAULT_LOCKING_RETRIES 300 /* 300 * 10ms */
#if GLIB_MAJOR_VERSION >= 2
# if GLIB_MINOR_VERSION >= 32
static GMutex file_ops_lock;
# define FILE_OPS_LOCK &file_ops_lock
# else
static GMutex *file_ops_lock;
# define FILE_OPS_LOCK file_ops_lock
# endif
#else
#error Unsupported glib version
#endif
struct cuse_param {
char *runas;
char *chroot;
char *logging;
char *keydata;
char *migkeydata;
char *piddata;
char *tpmstatedata;
char *localitydata;
char *seccompdata;
char *migrationdata;
unsigned int seccomp_action;
char *flagsdata;
uint16_t startupType;
};
/* single message to send to the worker thread */
static struct thread_message msg;
struct stateblob {
uint8_t type;
uint8_t *data;
uint32_t length;
TPM_BOOL is_encrypted;
};
typedef struct stateblob_desc {
uint32_t blobtype;
TPM_BOOL decrypt;
TPM_BOOL is_encrypted;
unsigned char *data;
uint32_t data_length;
} stateblob_desc;
typedef enum tx_state_type {
TX_STATE_CLOSED = 0,
TX_STATE_RW_COMMAND = 1,
TX_STATE_SET_STATE_BLOB = 2,
TX_STATE_GET_STATE_BLOB = 3,
} tx_state_type;
typedef struct transfer_state {
tx_state_type state;
/* while in TX_STATE_GET/SET_STATEBLOB */
uint32_t blobtype;
TPM_BOOL blob_is_encrypted;
/* while in TX_STATE_GET */
uint32_t offset;
} transfer_state;
typedef struct TPM_Response_Header {
uint16_t tag;
uint32_t paramSize;
uint32_t returnCode;
} __attribute__ ((packed)) TPM_Response_Header;
/*********************************** data *************************************/
static const char *usage =
"usage: %s %s [options]\n"
"\n"
"The following options are supported:\n"
"\n"
"-n NAME|--name=NAME : device name (mandatory)\n"
"-M MAJ|--maj=MAJ : device major number\n"
"-m MIN|--min=MIN : device minor number\n"
"--key file=<path>|fd=<fd>[,mode=aes-cbc|aes-256-cbc][,format=hex|binary][,remove=[true|false]]\n"
" : use an AES key for the encryption of the TPM's state\n"
" files; use the given mode for the block encryption;\n"
" the key is to be provided as a hex string or in binary\n"
" format; the keyfile can be automatically removed using\n"
" the remove parameter\n"
"--key pwdfile=<path>|pwdfd=<fd>[,mode=aes-cbc|aes-256-cbc][,remove=[true|false]][,kdf=sha512|pbkdf2]\n"
" : provide a passphrase in a file; the AES key will be\n"
" derived from this passphrase; default kdf is PBKDF2\n"
"--locality [reject-locality-4][,allow-set-locality]\n"
" : reject-locality-4: reject any command in locality 4\n"
" allow-set-locality: accept SetLocality command\n"
"--migration-key file=<path>|fd=<fd>[,mode=aes-cbc|aes-256-cbc][,format=hex|binary][,remove=[true|false]]\n"
" : use an AES key for the encryption of the TPM's state\n"
" when it is retrieved from the TPM via ioctls;\n"
" Setting this key ensures that the TPM's state will always\n"
" be encrypted when migrated\n"
"--migration-key pwdfile=<path>|pwdfd=<fd>[,mode=aes-cbc|aes-256-cbc][,remove=[true|false]][,kdf=sha512|pbkdf2]\n"
" : provide a passphrase in a file; the AES key will be\n"
" derived from this passphrase; default kdf is PBKDF2\n"
"--log file=<path>|fd=<filedescriptor>[,level=n][,prefix=<prefix>][,truncate]\n"
" : write the TPM's log into the given file rather than\n"
" to the console; provide '-' for path to avoid logging\n"
" log level 5 and higher will enable libtpms logging;\n"
" all logged output will be prefixed with prefix;\n"
" the log file can be reset (truncate)\n"
"--pid file=<path>|fd=<filedescriptor>\n"
" : write the process ID into the given file\n"
"--tpmstate dir=<dir>[,mode=0...]|backend-uri=<uri>\n"
" : set the directory or uri where the TPM's state will be written\n"
" into; the TPM_PATH environment variable can be used\n"
" instead of dir option;\n"
" mode allows a user to set the file mode bits of the state\n"
" files; the default mode is 0640;\n"
"--flags [not-need-init][,startup-clear|startup-state|startup-deactivated|startup-none][,disable-auto-shutdown]\n"
" : not-need-init: commands can be sent without needing to\n"
" send an INIT via control channel;\n"
" startup-...: send Startup command with this type;\n"
" disable-auto-shutdown disables automatic sending of\n"
" TPM2_Shutdown before TPM 2 reset or swtpm termination;\n"
"-r|--runas <user> : after creating the CUSE device, change to the given\n"
" user\n"
"-R|--chroot <path> : chroot to the given directory at startup\n"
"--tpm2 : choose TPM2 functionality\n"
#ifdef WITH_SECCOMP
# ifndef SCMP_ACT_LOG
"--seccomp action=none|kill\n"
# else
"--seccomp action=none|kill|log\n"
# endif
" : Choose the action of the seccomp profile when a\n"
" blacklisted syscall is executed; default is kill\n"
#endif
"--migration [incoming][,release-lock-outgoing]\n"
" : Incoming migration defers locking of storage backend\n"
" until the TPM state is received; release-lock-outgoing\n"
" releases the storage lock on outgoing migration\n"
"--print-capabilites : print capabilities and terminate\n"
"--print-states : print existing TPM states and terminate\n"
"-h|--help : display this help screen and terminate\n"
"\n";
static TPM_RESULT
ptm_io_getlocality(TPM_MODIFIER_INDICATOR *loc,
uint32_t tpmnum SWTPM_ATTR_UNUSED)
{
*loc = locality;
return TPM_SUCCESS;
}
static struct libtpms_callbacks cbs = {
.sizeOfStruct = sizeof(struct libtpms_callbacks),
.tpm_nvram_init = SWTPM_NVRAM_Init,
.tpm_nvram_loaddata = SWTPM_NVRAM_LoadData,
.tpm_nvram_storedata = SWTPM_NVRAM_StoreData,
.tpm_nvram_deletename = SWTPM_NVRAM_DeleteName,
.tpm_io_getlocality = ptm_io_getlocality,
};
/* the current state the transfer interface is in */
static transfer_state tx_state;
/* function prototypes */
static void ptm_cleanup(void);
/************************* NVRAM locking ************************************/
/* ensure that the NVRAM is locked; returns false in case of failure */
static bool ensure_locked_storage(void)
{
TPM_RESULT res;
if (g_storage_locked)
return true;
/* if NVRAM hasn't been initialized yet locking may need to be retried */
res = SWTPM_NVRAM_Lock_Storage(g_locking_retries);
if (res == TPM_RETRY)
return true;
if (res != TPM_SUCCESS)
return false;
g_storage_locked = true;
g_incoming_migration = false;
g_locking_retries = 0;
return true;
}
static void unlock_nvram(unsigned int locking_retries)
{
SWTPM_NVRAM_Unlock();
g_storage_locked = false;
g_locking_retries = locking_retries;
}
/************************* cached stateblob *********************************/
static stateblob_desc cached_stateblob;
/*
* cached_stateblob_is_loaded: is the stateblob with the given properties
* the one in the cache?
*/
static bool cached_stateblob_is_loaded(uint32_t blobtype,
TPM_BOOL decrypt)
{
return (cached_stateblob.data != NULL) &&
(cached_stateblob.blobtype == blobtype) &&
(cached_stateblob.decrypt == decrypt);
}
/*
* cached_stateblob_free: Free any previously loaded state blob
*/
static void cached_stateblob_free(void)
{
free(cached_stateblob.data);
cached_stateblob.data = NULL;
cached_stateblob.data_length = 0;
}
/*
* cached_stateblob_get_bloblength: get the total length of the cached blob
*/
static uint32_t cached_stateblob_get_bloblength(void)
{
return cached_stateblob.data_length;
}
/*
* cached_statblob_get: get stateblob data without copying them
*
* @offset: at which offset to get the data
* @bufptr: pointer to a buffer pointer used to return buffer start
* @length: pointer used to return number of available bytes in returned buffer
*/
static int cached_stateblob_get(uint32_t offset,
unsigned char **bufptr, size_t *length)
{
if (cached_stateblob.data == NULL ||
offset > cached_stateblob.data_length)
return -1;
*bufptr = &cached_stateblob.data[offset];
*length = cached_stateblob.data_length - offset;
return 0;
}
/*
* cached_stateblob_load: load a state blob into the cache
*
* blobtype: the type of blob
* decrypt: whether the blob is to be decrypted
*/
static TPM_RESULT cached_stateblob_load(uint32_t blobtype, TPM_BOOL decrypt)
{
TPM_RESULT res = 0;
const char *blobname = tpmlib_get_blobname(blobtype);
uint32_t tpm_number = 0;
if (!blobname)
return TPM_BAD_PARAMETER;
cached_stateblob_free();
if (blobtype == PTM_BLOB_TYPE_VOLATILE)
res = SWTPM_NVRAM_Store_Volatile();
if (res == 0)
res = SWTPM_NVRAM_GetStateBlob(&cached_stateblob.data,
&cached_stateblob.data_length,
tpm_number, blobname, decrypt,
&cached_stateblob.is_encrypted);
/* make sure the volatile state file is gone */
if (blobtype == PTM_BLOB_TYPE_VOLATILE)
SWTPM_NVRAM_DeleteName(tpm_number, blobname, FALSE);
if (res == 0) {
cached_stateblob.blobtype = blobtype;
cached_stateblob.decrypt = decrypt;
}
if (blobtype == PTM_BLOB_TYPE_SAVESTATE)
unlock_nvram(DEFAULT_LOCKING_RETRIES);
return res;
}
/*
* cached_state_blob_copy: copy the cached state blob to a destination buffer
*
* dest: destination buffer
* destlen: size of the buffer
* srcoffset: offset to copy from
* copied: variable to return the number of copied bytes
* is_encrypted: variable to return whether the blob is encrypted
*/
static int cached_stateblob_copy(void *dest, size_t destlen,
uint32_t srcoffset, uint32_t *copied,
TPM_BOOL *is_encrypted)
{
int ret = -1;
*copied = 0;
if (cached_stateblob.data != NULL && cached_stateblob.data_length > 0) {
if (srcoffset < cached_stateblob.data_length) {
*copied = min(cached_stateblob.data_length - srcoffset, destlen);
memcpy(dest, &cached_stateblob.data[srcoffset], *copied);
*is_encrypted = cached_stateblob.is_encrypted;
}
ret = 0;
}
return ret;
}
/************************* worker thread ************************************/
/*
* worker_thread: the worker thread
*/
static void worker_thread(gpointer data, gpointer user_data SWTPM_ATTR_UNUSED)
{
struct thread_message *msg = (struct thread_message *)data;
/* file_ops_lock not needed since thread_busy is set */
switch (msg->type) {
case MESSAGE_TPM_CMD:
TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
ptm_request, ptm_req_len);
ptm_read_offset = 0;
break;
case MESSAGE_IOCTL:
break;
}
/* results are ready */
worker_thread_mark_done();
}
/***************************** utility functions ****************************/
/*
* tpm_start: Start the TPM
*
* Check whether the TPM's state directory exists and if it does
* not exists, try to create it. Start the thread pool, initialize
* libtpms and allocate a global TPM request buffer.
*
* @flags: libtpms init flags
* @l_tpmversion: the version of the TPM
* @res: the result from starting the TPM
*/
static int tpm_start(uint32_t flags, TPMLIB_TPMVersion l_tpmversion,
TPM_RESULT *res)
{
DIR *dir;
const char *uri = tpmstate_get_backend_uri();
const char *tpmdir = uri + strlen("dir://");
*res = TPM_FAIL;
dir = opendir(tpmdir);
if (dir) {
closedir(dir);
} else {
if (mkdir(tpmdir, 0775)) {
logprintf(STDERR_FILENO,
"Error: Could not open tpmstate dir %s\n",
tpmdir);
return -1;
}
}
pool = g_thread_pool_new(worker_thread,
NULL,
1,
FALSE,
NULL);
if (!pool) {
logprintf(STDERR_FILENO,
"Error: Could not create the thread pool.\n");
return -1;
}
if(!ptm_request)
ptm_request = malloc(4096);
if(!ptm_request) {
logprintf(STDERR_FILENO,
"Error: Could not allocate memory for request buffer.\n");
goto error_del_pool;
}
g_storage_locked = !g_incoming_migration;
*res = tpmlib_start(flags, l_tpmversion, g_storage_locked);
if (*res != TPM_SUCCESS)
goto error_del_pool;
logprintf(STDOUT_FILENO,
"CUSE TPM successfully initialized.\n");
return 0;
error_del_pool:
g_thread_pool_free(pool, TRUE, TRUE);
pool = NULL;
return -1;
}
/*
* ptm_write_fatal_error_response: Write fatal error response
*
* Write a fatal error response into the global ptm_response buffer.
*/
static void ptm_write_fatal_error_response(TPMLIB_TPMVersion l_tpmversion)
{
tpmlib_write_fatal_error_response(&ptm_response,
&ptm_res_len,
&ptm_res_tot,
l_tpmversion);
ptm_read_offset = 0;
}
/*
* ptm_send_startup: Send a TPM/TPM2_Startup
*/
static int ptm_send_startup(uint16_t startupType,
TPMLIB_TPMVersion l_tpmversion SWTPM_ATTR_UNUSED)
{
uint32_t command_length;
unsigned char command[sizeof(struct tpm_startup)];
uint32_t max_command_length = sizeof(command);
int ret = 0;
TPM_RESULT rc = TPM_SUCCESS;
command_length = tpmlib_create_startup_cmd(
startupType,
tpmversion,
command, max_command_length);
if (command_length > 0) {
g_lastCommand = tpmlib_get_cmd_ordinal(command, command_length);
rc = TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
(unsigned char *)command, command_length);
ptm_read_offset = 0;
}
if (rc || command_length == 0) {
if (rc) {
logprintf(STDERR_FILENO, "Could not send Startup: 0x%x\n", rc);
ret = -1;
}
}
return ret;
}
/************************************ read() support ***************************/
/*
* ptm_read_result: Return the TPM response packet
*
* @req: the fuse_req_t
* @size: the max. number of bytes to return to the requester
*/
static void ptm_read_result(fuse_req_t req, size_t size)
{
size_t len = 0;
/* prevent other threads from reading or writing cmds or doing ioctls */
g_mutex_lock(FILE_OPS_LOCK);
if (tpm_running) {
/* wait until results are ready */
worker_thread_wait_done();
}
if (ptm_read_offset < ptm_res_len) {
len = ptm_res_len - ptm_read_offset;
if (size < len)
len = size;
}
fuse_reply_buf(req, (const char *)&ptm_response[ptm_read_offset], len);
ptm_read_offset += len;
g_mutex_unlock(FILE_OPS_LOCK);
}
/*
* ptm_read_stateblob: get a TPM stateblob via the read() interface
*
* @req: the fuse_req_t
* @size: the number of bytes to read
*
* The internal offset into the buffer is advanced by the number
* of bytes that were copied. We switch back to command read/write
* mode if an error occurred or once all bytes were read.
*/
static void ptm_read_stateblob(fuse_req_t req, size_t size)
{
unsigned char *bufptr = NULL;
size_t numbytes;
size_t tocopy;
if (cached_stateblob_get(tx_state.offset, &bufptr, &numbytes) < 0) {
fuse_reply_err(req, EIO);
tx_state.state = TX_STATE_RW_COMMAND;
} else {
tocopy = MIN(size, numbytes);
tx_state.offset += tocopy;
fuse_reply_buf(req, (char *)bufptr, tocopy);
/* last transfer indicated by less bytes available than requested */
if (numbytes < size) {
tx_state.state = TX_STATE_RW_COMMAND;
}
}
}
/*
* ptm_read: interface to POSIX read()
*
* @req: fuse_req_t
* @size: number of bytes to read
* @off: offset (not used)
* @fi: fuse_file_info (not used)
*
* Depending on the current state of the transfer interface (read/write)
* return either the results of TPM commands or a data of a TPM state blob.
*/
static void ptm_read(fuse_req_t req, size_t size, off_t off SWTPM_ATTR_UNUSED,
struct fuse_file_info *fi SWTPM_ATTR_UNUSED)
{
switch (tx_state.state) {
case TX_STATE_RW_COMMAND:
ptm_read_result(req, size);
break;
case TX_STATE_SET_STATE_BLOB:
fuse_reply_err(req, EIO);
tx_state.state = TX_STATE_RW_COMMAND;
break;
case TX_STATE_GET_STATE_BLOB:
ptm_read_stateblob(req, size);
break;
case TX_STATE_CLOSED:
/* not possible */
break;
}
}
/*************************read/write stateblob support ***********************/
/*
* ptm_set_stateblob_append: Append a piece of TPM state blob and transfer to TPM
*
* blobtype: the type of blob
* data: the data to append
* length: length of the data
* is_encrypted: whether the blob is encrypted
* is_last: whether this is the last part of the TPM state blob; if it is, the TPM
* state blob will then be transferred to the TPM
*/
static TPM_RESULT
ptm_set_stateblob_append(uint32_t blobtype,
const unsigned char *data, uint32_t length,
bool is_encrypted, bool is_last)
{
TPM_RESULT res = 0;
static struct stateblob stateblob;
unsigned char *tmp;
if (stateblob.type != blobtype) {
/* new blob; clear old data */
free(stateblob.data);
stateblob.data = NULL;
stateblob.length = 0;
stateblob.type = blobtype;
stateblob.is_encrypted = is_encrypted;
/*
* on the first call for a new state blob we allow 0 bytes to be written
* this allows the user to transfer via write()
*/
if (length == 0)
return 0;
}
/* append */
tmp = realloc(stateblob.data, stateblob.length + length);
if (!tmp) {
logprintf(STDERR_FILENO,
"Could not allocate %u bytes.\n", stateblob.length + length);
/* error */
free(stateblob.data);
stateblob.data = NULL;
stateblob.length = 0;
stateblob.type = 0;
return TPM_FAIL;
} else
stateblob.data = tmp;
memcpy(&stateblob.data[stateblob.length], data, length);
stateblob.length += length;
if (!is_last) {
/* full packet -- expecting more data */
return res;
}
res = SWTPM_NVRAM_SetStateBlob(stateblob.data,
stateblob.length,
stateblob.is_encrypted,
0 /* tpm_number */,
blobtype);
logprintf(STDERR_FILENO,
"Deserialized state type %d (%s), length=%d, res=%d\n",
blobtype, tpmlib_get_blobname(blobtype),
stateblob.length, res);
free(stateblob.data);
stateblob.data = NULL;
stateblob.length = 0;
stateblob.type = 0;
/* transfer of blob is complete */
tx_state.state = TX_STATE_RW_COMMAND;
return res;
}
/*
* ptm_set_stateblob: set part of a TPM state blob
*
* @req: fuse_req_t
* pss: ptm_setstate provided via ioctl()
*/
static void
ptm_set_stateblob(fuse_req_t req, ptm_setstate *pss)
{
TPM_RESULT res = 0;
TPM_BOOL is_encrypted =
((pss->u.req.state_flags & PTM_STATE_FLAG_ENCRYPTED) != 0);
bool is_last = (sizeof(pss->u.req.data) != pss->u.req.length);
if (pss->u.req.length > sizeof(pss->u.req.data)) {
res = TPM_BAD_PARAMETER;
goto send_response;
}
/* transfer of blob initiated */
tx_state.state = TX_STATE_SET_STATE_BLOB;
tx_state.blobtype = pss->u.req.type;
tx_state.blob_is_encrypted = is_encrypted;
tx_state.offset = 0;
res = ptm_set_stateblob_append(pss->u.req.type,
pss->u.req.data,
pss->u.req.length,
is_encrypted,
is_last);
if (res)
tx_state.state = TX_STATE_RW_COMMAND;
send_response:
pss->u.resp.tpm_result = res;
fuse_reply_ioctl(req, 0, pss, sizeof(*pss));
}
/*
* ptm_get_stateblob_part: get part of a state blob
*
* @blobtype: the type of blob to get
* @buffer: the buffer this function will write the blob into
* @buffer_size: the size of the buffer
* @offset: the offset into the state blob
* @copied: pointer to int to indicate the number of bytes that were copied
* @is_encryped: returns whether the state blob is encrypted
*/
static TPM_RESULT
ptm_get_stateblob_part(uint32_t blobtype,
unsigned char *buffer, size_t buffer_size,
uint32_t offset, uint32_t *copied,
TPM_BOOL decrypt, TPM_BOOL *is_encrypted)
{
TPM_RESULT res = 0;
if (!cached_stateblob_is_loaded(blobtype, decrypt)) {
res = cached_stateblob_load(blobtype, decrypt);
}
if (res == 0) {
cached_stateblob_copy(buffer, buffer_size,
offset, copied, is_encrypted);
}
return res;
}
/*
* ptm_get_stateblob: Get the state blob from the TPM using ioctl()
*/
static void
ptm_get_stateblob(fuse_req_t req, ptm_getstate *pgs)
{
TPM_RESULT res = 0;
uint32_t blobtype = pgs->u.req.type;
TPM_BOOL decrypt =
((pgs->u.req.state_flags & PTM_STATE_FLAG_DECRYPTED) != 0);
TPM_BOOL is_encrypted = FALSE;
uint32_t copied = 0;
uint32_t offset = pgs->u.req.offset;
uint32_t totlength;
res = ptm_get_stateblob_part(blobtype,
pgs->u.resp.data, sizeof(pgs->u.resp.data),
pgs->u.req.offset, &copied,
decrypt, &is_encrypted);
totlength = cached_stateblob_get_bloblength();
pgs->u.resp.state_flags = 0;
if (is_encrypted)
pgs->u.resp.state_flags |= PTM_STATE_FLAG_ENCRYPTED;
pgs->u.resp.length = copied;
pgs->u.resp.totlength = totlength;
pgs->u.resp.tpm_result = res;
logprintf(STDERR_FILENO,
"Serialized state type %d, length=%d, totlength=%d, res=%d\n",
blobtype, copied, totlength, res);
if (res == 0) {
if (offset + copied < totlength) {
/* last byte was not copied */
tx_state.state = TX_STATE_GET_STATE_BLOB;
tx_state.blobtype = pgs->u.req.type;
tx_state.blob_is_encrypted = is_encrypted;
tx_state.offset = copied;
} else {
/* last byte was copied */
tx_state.state = TX_STATE_RW_COMMAND;
}
} else {
/* error occurred */
tx_state.state = TX_STATE_RW_COMMAND;
}
fuse_reply_ioctl(req, 0, pgs, sizeof(pgs->u.resp));
}
/*********************************** write() support *************************/
/*
* ptm_write_stateblob: Write the state blob using the write() interface
*
* @req: the fuse_req_t
* @buf: the buffer with the data
* @size: the number of bytes in the buffer
*
* The data are appended to an existing buffer that was created with the
* initial ioctl().
*/
static void ptm_write_stateblob(fuse_req_t req, const char *buf, size_t size)
{
TPM_RESULT res;
res = ptm_set_stateblob_append(tx_state.blobtype,
(unsigned char *)buf, size,
tx_state.blob_is_encrypted,
(size == 0));
if (res) {
tx_state.state = TX_STATE_RW_COMMAND;
fuse_reply_err(req, EIO);
} else {
fuse_reply_write(req, size);
}
}
/*
* ptm_write_cmd: User writing a TPM command
*
* req: fuse_req_t
* buf: the buffer containing the TPM command
* size: the size of the buffer
* tpmversion: the version of the TPM
*/
static void ptm_write_cmd(fuse_req_t req, const char *buf, size_t size,
TPMLIB_TPMVersion l_tpmversion)
{
uint32_t lastCommand;
ptm_req_len = size;
ptm_res_len = 0;
/* prevent other threads from reading or writing cmds or doing ioctls */
g_mutex_lock(FILE_OPS_LOCK);
if (tpm_running) {
/* before processing a command ensure that the NVRAM is locked */
if (!ensure_locked_storage()) {
fuse_reply_err(req, EIO);
goto cleanup;
}
/* ensure that we only ever work on one TPM command */
if (worker_thread_is_busy()) {
fuse_reply_err(req, EBUSY);
goto cleanup;
}
if (ptm_req_len > TPM_REQ_MAX)
ptm_req_len = TPM_REQ_MAX;
/* process SetLocality command, if */
tpmlib_process(&ptm_response, &ptm_res_len, &ptm_res_tot,
(unsigned char *)buf, ptm_req_len,
locality_flags, &locality, tpmversion);
if (ptm_res_len) {
ptm_read_offset = 0;
goto skip_process;
}
lastCommand = tpmlib_get_cmd_ordinal((unsigned char *)buf, ptm_req_len);
if (lastCommand != TPM_ORDINAL_NONE)
g_lastCommand = lastCommand;
if (tpmlib_is_request_cancelable(l_tpmversion,
(const unsigned char*)buf,
ptm_req_len)) {
/* have command processed by thread pool */
memcpy(ptm_request, buf, ptm_req_len);
msg.type = MESSAGE_TPM_CMD;
worker_thread_mark_busy();
g_thread_pool_push(pool, &msg, NULL);
} else {
/* direct processing */
TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
(unsigned char *)buf, ptm_req_len);
ptm_read_offset = 0;
}
} else {
/* TPM not initialized; return error */
ptm_write_fatal_error_response(l_tpmversion);
}
skip_process:
fuse_reply_write(req, ptm_req_len);
cleanup:
g_mutex_unlock(FILE_OPS_LOCK);
return;
}
/*
* ptm_write: low-level write() interface; calls appropriate function depending
* on what is being transferred using the write()
*/
static void ptm_write(fuse_req_t req, const char *buf, size_t size,
off_t off SWTPM_ATTR_UNUSED,
struct fuse_file_info *fi SWTPM_ATTR_UNUSED)
{
switch (tx_state.state) {
case TX_STATE_RW_COMMAND:
ptm_write_cmd(req, buf, size, tpmversion);
break;
case TX_STATE_GET_STATE_BLOB:
fuse_reply_err(req, EIO);
tx_state.state = TX_STATE_RW_COMMAND;
break;
case TX_STATE_SET_STATE_BLOB:
ptm_write_stateblob(req, buf, size);
break;
case TX_STATE_CLOSED:
/* not possible */
break;
}
}
/*
* ptm_open: interface to POSIX open()
*/
static void ptm_open(fuse_req_t req, struct fuse_file_info *fi)
{
if (tx_state.state != TX_STATE_CLOSED) {
fuse_reply_err(req, EBUSY);
return;
}
tx_state.state = TX_STATE_RW_COMMAND;
fuse_reply_open(req, fi);
}
/*
* ptm_release:
*/
static void ptm_release(fuse_req_t req, struct fuse_file_info *fi)
{
tx_state.state = TX_STATE_CLOSED;
fuse_reply_err(req, 0);
}
/*
* ptm_ioctl : ioctl execution
*
* req: the fuse_req_t used to send response with
* cmd: the ioctl request code
* arg: the pointer the application used for calling the ioctl (3rd param)
* fi:
* flags: some flags provided by fuse
* in_buf: the copy of the input buffer
* in_bufsz: size of the input buffer; provided by fuse and has size of
* needed buffer
* out_bufsz: size of the output buffer; provided by fuse and has size of
* needed buffer
*/
static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
struct fuse_file_info *fi SWTPM_ATTR_UNUSED,
unsigned flags SWTPM_ATTR_UNUSED,
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
TPM_RESULT res = TPM_FAIL;
bool exit_prg = FALSE;
ptm_init *init_p;
TPM_MODIFIER_INDICATOR orig_locality;
/* prevent other threads from writing or doing ioctls */
g_mutex_lock(FILE_OPS_LOCK);
/* some commands have to wait until the worker thread is done */
switch(cmd) {
case PTM_GET_CAPABILITY:
case PTM_SET_LOCALITY:
case PTM_CANCEL_TPM_CMD:
case PTM_GET_CONFIG:
case PTM_SET_BUFFERSIZE:
case PTM_LOCK_STORAGE:
/* no need to wait */
break;
case PTM_INIT:
case PTM_SHUTDOWN:
case PTM_GET_TPMESTABLISHED:
case PTM_RESET_TPMESTABLISHED:
case PTM_HASH_START:
case PTM_HASH_DATA:
case PTM_HASH_END:
case PTM_STORE_VOLATILE:
case PTM_GET_STATEBLOB:
case PTM_SET_STATEBLOB:
if (tpm_running)
worker_thread_wait_done();
break;
}
switch (cmd) {
case PTM_GET_CAPABILITY:
if (out_bufsz != sizeof(ptm_cap)) {
struct iovec iov = { arg, sizeof(uint8_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_cap ptm_caps;
switch (tpmversion) {
case TPMLIB_TPM_VERSION_2:
ptm_caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN
| PTM_CAP_GET_TPMESTABLISHED
| PTM_CAP_SET_LOCALITY
| PTM_CAP_HASHING
| PTM_CAP_CANCEL_TPM_CMD
| PTM_CAP_STORE_VOLATILE
| PTM_CAP_RESET_TPMESTABLISHED
| PTM_CAP_GET_STATEBLOB
| PTM_CAP_SET_STATEBLOB
| PTM_CAP_STOP
| PTM_CAP_GET_CONFIG
| PTM_CAP_SET_BUFFERSIZE
| PTM_CAP_GET_INFO
| PTM_CAP_LOCK_STORAGE;
break;
case TPMLIB_TPM_VERSION_1_2:
ptm_caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN
| PTM_CAP_GET_TPMESTABLISHED
| PTM_CAP_SET_LOCALITY
| PTM_CAP_HASHING
| PTM_CAP_CANCEL_TPM_CMD
| PTM_CAP_STORE_VOLATILE
| PTM_CAP_RESET_TPMESTABLISHED
| PTM_CAP_GET_STATEBLOB
| PTM_CAP_SET_STATEBLOB
| PTM_CAP_STOP
| PTM_CAP_GET_CONFIG
| PTM_CAP_SET_BUFFERSIZE
| PTM_CAP_GET_INFO
| PTM_CAP_LOCK_STORAGE;
break;
}
fuse_reply_ioctl(req, 0, &ptm_caps, sizeof(ptm_caps));
}
break;
case PTM_INIT:
if (in_bufsz != sizeof(ptm_init)) {
struct iovec iov = { arg, sizeof(uint8_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
init_p = (ptm_init *)in_buf;
worker_thread_end();
if (tpm_running && !g_disable_auto_shutdown)
tpmlib_maybe_send_tpm2_shutdown(tpmversion,
&g_lastCommand);
TPMLIB_Terminate();
tpm_running = false;
if (tpm_start(init_p->u.req.init_flags, tpmversion, &res) < 0) {
logprintf(STDERR_FILENO,
"Error: Could not initialize the TPM.\n");
} else {
tpm_running = true;
}
init_p->u.resp.tpm_result = res;
fuse_reply_ioctl(req, 0, init_p, sizeof(*init_p));
}
break;
case PTM_STOP:
worker_thread_end();
if (tpm_running && !g_disable_auto_shutdown)
tpmlib_maybe_send_tpm2_shutdown(tpmversion, &g_lastCommand);
res = TPM_SUCCESS;
TPMLIB_Terminate();
tpm_running = false;
free(ptm_response);
ptm_response = NULL;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
break;
case PTM_SHUTDOWN:
worker_thread_end();
if (tpm_running && !g_disable_auto_shutdown)
tpmlib_maybe_send_tpm2_shutdown(tpmversion, &g_lastCommand);
res = TPM_SUCCESS;
TPMLIB_Terminate();
tpm_running = false;
free(ptm_response);
ptm_response = NULL;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
exit_prg = TRUE;
break;
case PTM_GET_TPMESTABLISHED:
if (!tpm_running)
goto error_not_running;
if (out_bufsz != sizeof(ptm_est)) {
struct iovec iov = { arg, sizeof(uint8_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_est te;
memset(&te, 0, sizeof(te));
te.u.resp.tpm_result = TPM_IO_TpmEstablished_Get(&te.u.resp.bit);
fuse_reply_ioctl(req, 0, &te, sizeof(te));
}
break;
case PTM_RESET_TPMESTABLISHED:
if (!tpm_running)
goto error_not_running;
if (in_bufsz != sizeof(ptm_reset_est)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_reset_est *re = (ptm_reset_est *)in_buf;
if (re->u.req.loc > 4) {
res = TPM_BAD_LOCALITY;
} else {
/* set locality and reset flag in one command */
orig_locality = locality;
locality = re->u.req.loc;
res = TPM_IO_TpmEstablished_Reset();
locality = orig_locality;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
}
}
break;
case PTM_SET_LOCALITY:
if (in_bufsz != sizeof(ptm_loc)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_loc *l = (ptm_loc *)in_buf;
if (l->u.req.loc > 4 ||
(l->u.req.loc == 4 &&
locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) {
res = TPM_BAD_LOCALITY;
} else {
res = TPM_SUCCESS;
locality = l->u.req.loc;
}
l->u.resp.tpm_result = res;
fuse_reply_ioctl(req, 0, l, sizeof(*l));
}
break;
case PTM_HASH_START:
if (!tpm_running)
goto error_not_running;
res = TPM_IO_Hash_Start();
fuse_reply_ioctl(req, 0, &res, sizeof(res));
break;
case PTM_HASH_DATA:
if (!tpm_running)
goto error_not_running;
if (in_bufsz != sizeof(ptm_hdata)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_hdata *data = (ptm_hdata *)in_buf;
if (data->u.req.length <= sizeof(data->u.req.data)) {
res = TPM_IO_Hash_Data(data->u.req.data,
data->u.req.length);
} else {
res = TPM_FAIL;
}
data->u.resp.tpm_result = res;
fuse_reply_ioctl(req, 0, data, sizeof(*data));
}
break;
case PTM_HASH_END:
if (!tpm_running)
goto error_not_running;
res = TPM_IO_Hash_End();
fuse_reply_ioctl(req, 0, &res, sizeof(res));
break;
case PTM_CANCEL_TPM_CMD:
if (!tpm_running)
goto error_not_running;
/* for cancellation to work, the TPM would have to
* execute in another thread that polls on a cancel
* flag
*/
res = TPMLIB_CancelCommand();
fuse_reply_ioctl(req, 0, &res, sizeof(res));
break;
case PTM_STORE_VOLATILE:
if (!tpm_running)
goto error_not_running;
res = SWTPM_NVRAM_Store_Volatile();
fuse_reply_ioctl(req, 0, &res, sizeof(res));
cached_stateblob_free();
break;
case PTM_GET_STATEBLOB:
if (!tpm_running)
goto error_not_running;
if (in_bufsz != sizeof(ptm_getstate)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_get_stateblob(req, (ptm_getstate *)in_buf);
}
break;
case PTM_SET_STATEBLOB:
if (tpm_running)
goto error_running;
/* tpm state dir must be set */
SWTPM_NVRAM_Init();
if ((exit_prg = !ensure_locked_storage())) {
fuse_reply_err(req, EIO);
} else if (in_bufsz != sizeof(ptm_setstate)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_set_stateblob(req, (ptm_setstate *)in_buf);
}
break;
case PTM_GET_CONFIG:
if (out_bufsz != sizeof(ptm_getconfig)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_getconfig pgs;
pgs.u.resp.tpm_result = 0;
pgs.u.resp.flags = 0;
if (SWTPM_NVRAM_Has_FileKey())
pgs.u.resp.flags |= PTM_CONFIG_FLAG_FILE_KEY;
if (SWTPM_NVRAM_Has_MigrationKey())
pgs.u.resp.flags |= PTM_CONFIG_FLAG_MIGRATION_KEY;
fuse_reply_ioctl(req, 0, &pgs, sizeof(pgs));
}
break;
case PTM_SET_BUFFERSIZE:
if (out_bufsz != sizeof(ptm_setbuffersize)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_setbuffersize *in_psbs = (ptm_setbuffersize *)in_buf;
ptm_setbuffersize out_psbs;
uint32_t buffersize, minsize, maxsize;
buffersize = in_psbs->u.req.buffersize;
if (buffersize > 0 && tpm_running)
goto error_running;
buffersize = TPMLIB_SetBufferSize(buffersize,
&minsize,
&maxsize);
out_psbs.u.resp.tpm_result = TPM_SUCCESS;
out_psbs.u.resp.buffersize = buffersize;
out_psbs.u.resp.minsize = minsize;
out_psbs.u.resp.maxsize = maxsize;
fuse_reply_ioctl(req, 0, &out_psbs, sizeof(out_psbs));
}
break;
case PTM_GET_INFO:
if (out_bufsz != sizeof(ptm_getinfo)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_getinfo *in_pgi = (ptm_getinfo *)in_buf;
ptm_getinfo out_pgi;
char *info_data;
uint32_t length, offset;
info_data = TPMLIB_GetInfo(in_pgi->u.req.flags);
if (!info_data)
goto error_memory;
offset = in_pgi->u.req.offset;
if (offset >= strlen(info_data)) {
free(info_data);
goto error_bad_input;
}
length = min(strlen(info_data) + 1 - offset,
sizeof(out_pgi.u.resp.buffer));
out_pgi.u.resp.tpm_result = 0;
out_pgi.u.resp.totlength = strlen(info_data) + 1;
out_pgi.u.resp.length = length;
/* client has to collect whole string in case buffer is too small */
memcpy(out_pgi.u.resp.buffer, &info_data[offset], length);
free(info_data);
fuse_reply_ioctl(req, 0, &out_pgi, sizeof(out_pgi));
}
break;
case PTM_LOCK_STORAGE:
if (out_bufsz != sizeof(ptm_lockstorage)) {
struct iovec iov = { arg, sizeof(uint32_t) };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
} else {
ptm_lockstorage *in_pls = (ptm_lockstorage *)in_buf;
ptm_lockstorage out_pls;
g_locking_retries = in_pls->u.req.retries;
if (!ensure_locked_storage())
out_pls.u.resp.tpm_result = TPM_FAIL;
else
out_pls.u.resp.tpm_result = TPM_SUCCESS;
fuse_reply_ioctl(req, 0, &out_pls, sizeof(out_pls));
}
break;
default:
fuse_reply_err(req, EINVAL);
}
cleanup:
g_mutex_unlock(FILE_OPS_LOCK);
if (exit_prg) {
logprintf(STDOUT_FILENO, "CUSE TPM is shutting down.\n");
ptm_cleanup();
fuse_session_exit(ptm_fuse_session);
}
return;
error_bad_input:
res = TPM_BAD_PARAMETER;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
goto cleanup;
error_running:
error_not_running:
res = TPM_BAD_ORDINAL;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
goto cleanup;
error_memory:
res = TPM_SIZE;
fuse_reply_ioctl(req, 0, &res, sizeof(res));
goto cleanup;
}
static void ptm_init_done(void *userdata)
{
struct cuse_param *param = userdata;
int ret;
/* at this point the entry in /dev/ is available */
if (pidfile_write(getpid()) < 0) {
ret = -13;
goto error_exit;
}
if (param->runas) {
ret = change_process_owner(param->runas);
if (ret)
goto error_exit;
}
if (create_seccomp_profile(true, param->seccomp_action) < 0) {
ret = -14;
goto error_exit;
}
return;
error_exit:
ptm_cleanup();
exit(ret);
}
static void ptm_cleanup(void)
{
pidfile_remove();
log_global_free();
tpmstate_global_free();
SWTPM_NVRAM_Shutdown();
}
static const struct cuse_lowlevel_ops clops = {
.open = ptm_open,
.read = ptm_read,
.write = ptm_write,
.release = ptm_release,
.ioctl = ptm_ioctl,
.init_done = ptm_init_done,
};
/* ptm_cuse_lowlevel_main is like cuse_lowlevel_main with the difference that
* it uses a global ptm_fuse_session so we can call fuse_session_exit() on it
* for a graceful exit with cleanups.
*/
static int
ptm_cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
const struct cuse_lowlevel_ops *clop, void *userdata)
{
int mt;
int ret;
struct cuse_param *param = userdata;
ptm_fuse_session = cuse_lowlevel_setup(argc, argv, ci, clop, &mt,
userdata);
if (ptm_fuse_session == NULL)
return 1;
if (param->seccomp_action == SWTPM_SECCOMP_ACTION_NONE && mt)
ret = fuse_session_loop_mt(ptm_fuse_session);
else
ret = fuse_session_loop(ptm_fuse_session);
cuse_lowlevel_teardown(ptm_fuse_session);
if (ret < 0)
ret = 1;
return ret;
}
#ifndef HAVE_SWTPM_CUSE_MAIN
int main(int argc, char **argv)
{
const char *prgname = argv[0];
const char *iface = "";
#else
int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *iface)
{
#endif
int opt, longindex = 0;
static struct option longopts[] = {
{"maj" , required_argument, 0, 'M'},
{"min" , required_argument, 0, 'm'},
{"name" , required_argument, 0, 'n'},
{"runas" , required_argument, 0, 'r'},
{"chroot" , required_argument, 0, 'R'},
{"log" , required_argument, 0, 'l'},
{"locality" , required_argument, 0, 'L'},
{"key" , required_argument, 0, 'k'},
{"migration-key" , required_argument, 0, 'K'},
{"pid" , required_argument, 0, 'p'},
{"tpmstate" , required_argument, 0, 's'},
{"flags" , required_argument, 0, 'F'},
{"tpm2" , no_argument, 0, '2'},
{"help" , no_argument, 0, 'h'},
{"version" , no_argument, 0, 'v'},
#ifdef WITH_SECCOMP
{"seccomp" , required_argument, 0, 'S'},
#endif
{"migration" , required_argument, 0, 'i'},
{"print-capabilities"
, no_argument, 0, 'a'},
{"print-states" , no_argument, 0, 'e'},
{NULL , 0 , 0, 0 },
};
struct cuse_info cinfo;
struct cuse_param param = {
.startupType = _TPM_ST_NONE,
};
const char *devname = NULL;
char *cinfo_argv[1] = { 0 };
unsigned int num;
struct passwd *passwd;
const char *uri = NULL;
int n, tpmfd;
char path[PATH_MAX];
int ret = 0;
bool printcapabilities = false;
bool printstates = false;
bool need_init_cmd = true;
TPM_RESULT res;
memset(&cinfo, 0, sizeof(cinfo));
memset(&param, 0, sizeof(param));
#if GLIB_MINOR_VERSION >= 32
g_mutex_init(FILE_OPS_LOCK);
#else
FILE_OPS_LOCK = g_mutex_new();
#endif
log_set_prefix("swtpm: ");
tpmversion = TPMLIB_TPM_VERSION_1_2;
while (true) {
opt = getopt_long(argc, argv, "M:m:n:r:R:hv", longopts, &longindex);
if (opt == -1)
break;
switch (opt) {
case 'M': /* major */
if (sscanf(optarg, "%u", &num) != 1) {
logprintf(STDERR_FILENO, "Could not parse major number\n");
ret = -1;
goto exit;
}
if (num > 65535) {
logprintf(STDERR_FILENO,
"Major number outside valid range [0 - 65535]\n");
ret = -1;
goto exit;
}
cinfo.dev_major = num;
break;
case 'm': /* minor */
if (sscanf(optarg, "%u", &num) != 1) {
logprintf(STDERR_FILENO, "Could not parse major number\n");
ret = -1;
goto exit;
}
if (num > 65535) {
logprintf(STDERR_FILENO,
"Major number outside valid range [0 - 65535]\n");
ret = -1;
goto exit;
}
cinfo.dev_minor = num;
break;
case 'n': /* name */
if (!cinfo.dev_info_argc) {
cinfo_argv[0] = calloc(1, strlen("DEVNAME=") + strlen(optarg) + 1);
if (!cinfo_argv[0]) {
logprintf(STDERR_FILENO, "Out of memory\n");
ret = -1;
goto exit;
}
devname = optarg;
strcpy(cinfo_argv[0], "DEVNAME=");
strcat(cinfo_argv[0], optarg);
cinfo.dev_info_argc = 1;
cinfo.dev_info_argv = (const char **)cinfo_argv;
}
break;
case 'r': /* runas */
param.runas = optarg;
break;
case 'R':
param.chroot = optarg;
break;
case 'l': /* log */
param.logging = optarg;
break;
case 'k': /* key */
param.keydata = optarg;
break;
case 'K': /* migration-key */
param.migkeydata = optarg;
break;
case 'p': /* pid */
param.piddata = optarg;
break;
case 's': /* tpmstate */
param.tpmstatedata = optarg;
break;
case 'L':
param.localitydata = optarg;
break;
case 'F':
param.flagsdata = optarg;
break;
case '2':
tpmversion = TPMLIB_TPM_VERSION_2;
break;
case 'S':
param.seccompdata = optarg;
break;
case 'i': /* --migration */
param.migrationdata = optarg;
break;
case 'h': /* help */
fprintf(stdout, usage, prgname, iface);
goto exit;
case 'a':
printcapabilities = true;
break;
case 'e':
printstates = true;
break;
case 'v': /* version */
fprintf(stdout, "TPM emulator CUSE interface version %d.%d.%d, "
"Copyright (c) 2014-2015 IBM Corp.\n",
SWTPM_VER_MAJOR,
SWTPM_VER_MINOR,
SWTPM_VER_MICRO);
goto exit;
}
}
if (optind < argc) {
logprintf(STDERR_FILENO,
"Unknown parameter '%s'\n", argv[optind]);
ret = EXIT_FAILURE;
goto exit;
}
if (setuid(0)) {
logprintf(STDERR_FILENO, "Error: Unable to setuid root. uid = %d, "
"euid = %d, gid = %d\n", getuid(), geteuid(), getgid());
ret = -4;
goto exit;
}
if (param.chroot) {
if (do_chroot(param.chroot) < 0) {
ret = EXIT_FAILURE;
goto exit;
}
}
if (param.runas) {
if (!(passwd = getpwnam(param.runas))) {
logprintf(STDERR_FILENO, "User '%s' does not exist\n",
param.runas);
ret = -5;
goto exit;
}
}
if (handle_log_options(param.logging) < 0) {
ret = EXIT_FAILURE;
goto exit;
}
if (printcapabilities) {
/*
* Choose the TPM version so that getting/setting buffer size works.
* Ignore failure, for backward compatibility when TPM 1.2 is disabled.
*/
ret = capabilities_print_json(true, tpmversion) ? EXIT_FAILURE : EXIT_SUCCESS;
goto exit;
}
if (tpmlib_choose_tpm_version(tpmversion) != TPM_SUCCESS) {
ret = EXIT_FAILURE;
goto exit;
}
tpmstate_set_version(tpmversion);
if (printstates) {
if (handle_tpmstate_options(param.tpmstatedata) < 0) {
ret = EXIT_FAILURE;
goto exit;
}
if (param.tpmstatedata == NULL) {
logprintf(STDERR_FILENO,
"Error: --tpmstate option is required for --print-states\n");
ret = EXIT_FAILURE;
goto exit;
}
ret = SWTPM_NVRAM_PrintJson();
ret = ret ? EXIT_FAILURE : EXIT_SUCCESS;
goto exit;
}
if (!cinfo.dev_info_argv) {
logprintf(STDERR_FILENO, "Error: device name missing\n");
ret = -2;
goto exit;
}
if (handle_key_options(param.keydata) < 0 ||
handle_migration_key_options(param.migkeydata) < 0 ||
handle_pid_options(param.piddata) < 0 ||
handle_tpmstate_options(param.tpmstatedata) < 0 ||
handle_seccomp_options(param.seccompdata, &param.seccomp_action) < 0 ||
handle_locality_options(param.localitydata, &locality_flags) < 0 ||
handle_flags_options(param.flagsdata, &need_init_cmd,
&param.startupType, &g_disable_auto_shutdown) < 0 ||
handle_migration_options(param.migrationdata, &g_incoming_migration,
&g_release_lock_outgoing) < 0) {
ret = -3;
goto exit;
}
uri = tpmstate_get_backend_uri();
if (uri == NULL) {
logprintf(STDERR_FILENO,
"Error: No TPM state directory is defined; "
"TPM_PATH is not set\n");
ret = -1;
goto exit;
}
n = snprintf(path, sizeof(path), "/dev/%s", devname);
if (n < 0) {
logprintf(STDERR_FILENO,
"Error: Could not create device file name\n");
ret = -1;
goto exit;
}
if (n >= (int)sizeof(path)) {
logprintf(STDERR_FILENO,
"Error: Buffer too small to create device file name\n");
ret = -1;
goto exit;
}
tpmfd = open(path, O_RDWR);
if (tpmfd >= 0) {
close(tpmfd);
logprintf(STDERR_FILENO,
"Error: A device '%s' already exists.\n",
path);
ret = -1;
goto exit;
}
if (tpmlib_register_callbacks(&cbs) != TPM_SUCCESS) {
ret = -1;
goto exit;
}
worker_thread_init();
g_mutex_lock(FILE_OPS_LOCK);
if (!need_init_cmd) {
if (tpm_start(0, tpmversion, &res) < 0) {
ret = -1;
goto err_unlock;
}
tpm_running = true;
}
if (param.startupType != _TPM_ST_NONE) {
if (ptm_send_startup(param.startupType, tpmversion) < 0) {
ret = -1;
goto err_unlock;
}
}
g_mutex_unlock(FILE_OPS_LOCK);
ret = ptm_cuse_lowlevel_main(1, argv, &cinfo, &clops, &param);
exit:
ptm_cleanup();
free(cinfo_argv[0]);
#if GLIB_MINOR_VERSION < 32
g_mutex_free(FILE_OPS_LOCK);
#endif
return ret;
err_unlock:
g_mutex_unlock(FILE_OPS_LOCK);
goto exit;
}