]> git.proxmox.com Git - swtpm.git/blobdiff - src/swtpm/cuse_tpm.c
swtpm: Rename disable_fips_mode() and move into tpmlib_start()
[swtpm.git] / src / swtpm / cuse_tpm.c
index 592133f25976f0aa9c0c7afa162bd77bf64ac865..e73b413cca8016cef5e70e2ec28701efcfa32059 100644 (file)
 #include "common.h"
 #include "tpmstate.h"
 #include "pidfile.h"
+#include "locality.h"
 #include "logging.h"
 #include "tpm_ioctl.h"
-#include "swtpm_nvfile.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 */
 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;
 
@@ -93,15 +104,21 @@ static TPM_MODIFIER_INDICATOR locality;
 /* whether the TPM is running (TPM_Init was received) */
 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;
+
 #if GLIB_MAJOR_VERSION >= 2
 # if GLIB_MINOR_VERSION >= 32
 
-GMutex file_ops_lock;
+static GMutex file_ops_lock;
 #  define FILE_OPS_LOCK &file_ops_lock
 
 # else
 
-GMutex *file_ops_lock;
+static GMutex *file_ops_lock;
 #  define FILE_OPS_LOCK file_ops_lock
 
 # endif
@@ -118,13 +135,16 @@ struct cuse_param {
     char *migkeydata;
     char *piddata;
     char *tpmstatedata;
+    char *localitydata;
+    char *seccompdata;
+    unsigned int seccomp_action;
+    char *flagsdata;
+    uint16_t startupType;
 };
 
 /* single message to send to the worker thread */
 static struct thread_message msg;
 
-#define min(a,b) ((a) < (b) ? (a) : (b))
-
 struct stateblob {
     uint8_t type;
     uint8_t *data;
@@ -141,6 +161,7 @@ typedef struct stateblob_desc {
 } 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,
@@ -171,52 +192,64 @@ static const char *usage =
 "-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>[,mode=aes-cbc][,format=hex|binary][,remove=[true|false]]\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>[,mode=aes-cbc][,remove=[true|false]]\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\n"
-"--migration-key file=<path>[,mode=aes-cbc][,format=hex|binary][,remove=[true|false]]\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>[,mode=aes-cbc][,remove=[true|false]]\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\n"
-"--log file=<path>|fd=<filedescriptor>[,level=n]\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"
-"--pid file=<path>   :  write the process ID into the given file\n"
-"--tpmstate dir=<dir>\n"
-"                    :  set the directory where the TPM's state will be written\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\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]\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"
 "-r|--runas <user>   :  after creating the CUSE device, change to the given\n"
 "                       user\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
+"--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";
 
-const static unsigned char TPM_Resp_FatalError[] = {
-    0x00, 0xC4,                     /* TPM Response */
-    0x00, 0x00, 0x00, 0x0A,         /* length (10) */
-    0x00, 0x00, 0x00, 0x09          /* TPM_FAIL */
-};
-
-const static unsigned char TPM_ResetEstablishmentBit[] = {
-    0x00, 0xC1,                     /* TPM Request */
-    0x00, 0x00, 0x00, 0x0A,         /* length (10) */
-    0x40, 0x00, 0x00, 0x0B          /* TPM_ORD_ResetEstablishmentBit */
-};
-
 static TPM_RESULT
-ptm_io_getlocality(TPM_MODIFIER_INDICATOR *loc, uint32_t tpmnum)
+ptm_io_getlocality(TPM_MODIFIER_INDICATOR *loc,
+                   uint32_t tpmnum SWTPM_ATTR_UNUSED)
 {
     *loc = locality;
     return TPM_SUCCESS;
@@ -234,6 +267,9 @@ static struct libtpms_callbacks cbs = {
 /* the current state the transfer interface is in */
 static transfer_state tx_state;
 
+/* function prototypes */
+static void ptm_cleanup(void);
+
 /************************* cached stateblob *********************************/
 
 static stateblob_desc cached_stateblob;
@@ -255,7 +291,7 @@ static bool cached_stateblob_is_loaded(uint32_t blobtype,
  */
 static void cached_stateblob_free(void)
 {
-    TPM_Free(cached_stateblob.data);
+    free(cached_stateblob.data);
     cached_stateblob.data = NULL;
     cached_stateblob.data_length = 0;
 }
@@ -364,7 +400,7 @@ static int cached_stateblob_copy(void *dest, size_t destlen,
 /*
  * worker_thread: the worker thread
  */
-static void worker_thread(gpointer data, gpointer user_data)
+static void worker_thread(gpointer data, gpointer user_data SWTPM_ATTR_UNUSED)
 {
     struct thread_message *msg = (struct thread_message *)data;
 
@@ -372,6 +408,7 @@ static void worker_thread(gpointer data, gpointer user_data)
     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;
@@ -383,43 +420,6 @@ static void worker_thread(gpointer data, gpointer user_data)
 
 /***************************** utility functions ****************************/
 
-/* _TPM_IO_TpmEstablished_Reset
- *
- * Reset the TPM Established bit by creating a TPM_ResetEstablishmentBit
- * command and sending it to the TPM; we temporarily switch the locality
- * to the one provded to this call. We wait until the TPM has processed
- * the request.
- */
-static TPM_RESULT _TPM_IO_TpmEstablished_Reset(fuse_req_t req,
-                                                TPM_MODIFIER_INDICATOR locty)
-{
-    TPM_RESULT res = TPM_FAIL;
-    TPM_Response_Header *tpmrh;
-    TPM_MODIFIER_INDICATOR orig_locality = locality;
-
-    locality = locty;
-
-    ptm_req_len = sizeof(TPM_ResetEstablishmentBit);
-    memcpy(ptm_request, TPM_ResetEstablishmentBit, ptm_req_len);
-
-    msg.type = MESSAGE_TPM_CMD;
-
-    worker_thread_mark_busy();
-
-    g_thread_pool_push(pool, &msg, NULL);
-
-    worker_thread_wait_done();
-
-    if (ptm_res_len >= sizeof(TPM_Response_Header)) {
-        tpmrh = (TPM_Response_Header *)ptm_response;
-        res = ntohl(tpmrh->returnCode);
-    }
-
-    locality = orig_locality;
-
-    return res;
-}
-
 /*
  * tpm_start: Start the TPM
  *
@@ -428,11 +428,17 @@ static TPM_RESULT _TPM_IO_TpmEstablished_Reset(fuse_req_t req,
  * 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)
+static int tpm_start(uint32_t flags, TPMLIB_TPMVersion l_tpmversion,
+                     TPM_RESULT *res)
 {
     DIR *dir;
-    const char *tpmdir = tpmstate_get_dir();
+    const char *uri = tpmstate_get_backend_uri();
+    const char *tpmdir = uri + strlen("dir://");
+
+    *res = TPM_FAIL;
 
     dir = opendir(tpmdir);
     if (dir) {
@@ -449,7 +455,7 @@ static int tpm_start(uint32_t flags)
     pool = g_thread_pool_new(worker_thread,
                              NULL,
                              1,
-                             TRUE,
+                             FALSE,
                              NULL);
     if (!pool) {
         logprintf(STDERR_FILENO,
@@ -465,7 +471,8 @@ static int tpm_start(uint32_t flags)
         goto error_del_pool;
     }
 
-    if (tpmlib_start(&cbs, flags) != TPM_SUCCESS)
+    *res = tpmlib_start(flags, l_tpmversion);
+    if (*res != TPM_SUCCESS)
         goto error_del_pool;
 
     logprintf(STDOUT_FILENO,
@@ -485,19 +492,45 @@ error_del_pool:
  *
  * Write a fatal error response into the global ptm_response buffer.
  */
-static void ptm_write_fatal_error_response(void)
+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)
 {
-    if (ptm_response == NULL ||
-        ptm_res_tot < sizeof(TPM_Resp_FatalError)) {
-        ptm_res_tot = sizeof(TPM_Resp_FatalError);
-        TPM_Realloc(&ptm_response, ptm_res_tot);
+    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) {
+        rc = TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
+                           (unsigned char *)command, command_length);
+        ptm_read_offset = 0;
     }
-    if (ptm_response) {
-        ptm_res_len = sizeof(TPM_Resp_FatalError);
-        memcpy(ptm_response,
-               TPM_Resp_FatalError,
-               sizeof(TPM_Resp_FatalError));
+
+    if (rc || command_length == 0) {
+        if (rc) {
+            logprintf(STDERR_FILENO, "Could not send Startup: 0x%x\n", rc);
+            ret = -1;
+        }
     }
+
+    return ret;
 }
 
 /************************************ read() support ***************************/
@@ -510,23 +543,27 @@ static void ptm_write_fatal_error_response(void)
  */
 static void ptm_read_result(fuse_req_t req, size_t size)
 {
-    int len;
+    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();
     }
 
-    len = ptm_res_len;
-
-    if (ptm_res_len > size) {
-        len = size;
-        ptm_res_len -= size;
-    } else {
-        ptm_res_len = 0;
+    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, len);
+    fuse_reply_buf(req, (const char *)&ptm_response[ptm_read_offset], len);
+
+    ptm_read_offset += len;
+
+    g_mutex_unlock(FILE_OPS_LOCK);
 }
 
 /*
@@ -571,8 +608,8 @@ static void ptm_read_stateblob(fuse_req_t req, size_t size)
  * 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,
-                     struct fuse_file_info *fi)
+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:
@@ -585,6 +622,9 @@ static void ptm_read(fuse_req_t req, size_t size, off_t off,
     case TX_STATE_GET_STATE_BLOB:
         ptm_read_stateblob(req, size);
         break;
+    case TX_STATE_CLOSED:
+        /* not possible */
+        break;
     }
 }
 
@@ -605,13 +645,13 @@ ptm_set_stateblob_append(uint32_t blobtype,
                          const unsigned char *data, uint32_t length,
                          bool is_encrypted, bool is_last)
 {
-    const char *blobname;
     TPM_RESULT res = 0;
     static struct stateblob stateblob;
+    unsigned char *tmp;
 
     if (stateblob.type != blobtype) {
         /* new blob; clear old data */
-        TPM_Free(stateblob.data);
+        free(stateblob.data);
         stateblob.data = NULL;
         stateblob.length = 0;
         stateblob.type = blobtype;
@@ -626,16 +666,19 @@ ptm_set_stateblob_append(uint32_t blobtype,
     }
 
     /* append */
-    res = TPM_Realloc(&stateblob.data, stateblob.length + length);
-    if (res != 0) {
+    tmp = realloc(stateblob.data, stateblob.length + length);
+    if (!tmp) {
+        logprintf(STDERR_FILENO,
+                  "Could not allocate %u bytes.\n", stateblob.length + length);
         /* error */
-        TPM_Free(stateblob.data);
+        free(stateblob.data);
         stateblob.data = NULL;
         stateblob.length = 0;
         stateblob.type = 0;
 
-        return res;
-    }
+        return TPM_FAIL;
+    } else
+        stateblob.data = tmp;
 
     memcpy(&stateblob.data[stateblob.length], data, length);
     stateblob.length += length;
@@ -644,19 +687,19 @@ ptm_set_stateblob_append(uint32_t blobtype,
         /* full packet -- expecting more data */
         return res;
     }
-    blobname = tpmlib_get_blobname(blobtype);
-
-    if (blobname) {
-        res = SWTPM_NVRAM_SetStateBlob(stateblob.data,
-                                       stateblob.length,
-                                       stateblob.is_encrypted,
-                                       0 /* tpm_number */,
-                                       blobname);
-    } else {
-        res = TPM_BAD_PARAMETER;
-    }
 
-    TPM_Free(stateblob.data);
+    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;
@@ -766,6 +809,9 @@ ptm_get_stateblob(fuse_req_t req, ptm_getstate *pgs)
     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) {
@@ -820,13 +866,15 @@ static void ptm_write_stateblob(fuse_req_t req, const char *buf, size_t size)
  * 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)
+static void ptm_write_cmd(fuse_req_t req, const char *buf, size_t size,
+                          TPMLIB_TPMVersion l_tpmversion)
 {
     ptm_req_len = size;
     ptm_res_len = 0;
 
-    /* prevent other threads from writing or doing ioctls */
+    /* prevent other threads from reading or writing cmds or doing ioctls */
     g_mutex_lock(FILE_OPS_LOCK);
 
     if (tpm_running) {
@@ -839,7 +887,18 @@ static void ptm_write_cmd(fuse_req_t req, const char *buf, size_t size)
         if (ptm_req_len > TPM_REQ_MAX)
             ptm_req_len = TPM_REQ_MAX;
 
-        if (tpmlib_is_request_cancelable(ptm_request, ptm_req_len)) {
+        /* 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;
+        }
+
+        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);
 
@@ -852,12 +911,14 @@ static void ptm_write_cmd(fuse_req_t req, const char *buf, size_t size)
             /* 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();
+        ptm_write_fatal_error_response(l_tpmversion);
     }
 
+skip_process:
     fuse_reply_write(req, ptm_req_len);
 
 cleanup:
@@ -871,11 +932,12 @@ cleanup:
  *            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, struct fuse_file_info *fi)
+                      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);
+        ptm_write_cmd(req, buf, size, tpmversion);
         break;
     case TX_STATE_GET_STATE_BLOB:
         fuse_reply_err(req, EIO);
@@ -884,6 +946,9 @@ static void ptm_write(fuse_req_t req, const char *buf, size_t size,
     case TX_STATE_SET_STATE_BLOB:
         ptm_write_stateblob(req, buf, size);
         break;
+    case TX_STATE_CLOSED:
+        /* not possible */
+        break;
     }
 }
 
@@ -892,11 +957,26 @@ static void ptm_write(fuse_req_t req, const char *buf, size_t size,
  */
 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
  *
@@ -912,17 +992,14 @@ static void ptm_open(fuse_req_t req, struct fuse_file_info *fi)
  *            needed buffer
  */
 static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
-                      struct fuse_file_info *fi, unsigned flags,
+                      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;
-
-    if (flags & FUSE_IOCTL_COMPAT) {
-        fuse_reply_err(req, ENOSYS);
-        return;
-    }
+    TPM_MODIFIER_INDICATOR orig_locality;
 
     /* some commands have to wait until the worker thread is done */
     switch(cmd) {
@@ -930,6 +1007,7 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
     case PTM_SET_LOCALITY:
     case PTM_CANCEL_TPM_CMD:
     case PTM_GET_CONFIG:
+    case PTM_SET_BUFFERSIZE:
         /* no need to wait */
         break;
     case PTM_INIT:
@@ -957,17 +1035,38 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
             fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
         } else {
             ptm_cap ptm_caps;
-            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;
+            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;
+                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;
+                break;
+            }
             fuse_reply_ioctl(req, 0, &ptm_caps, sizeof(ptm_caps));
         }
         break;
@@ -984,12 +1083,10 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
             TPMLIB_Terminate();
 
             tpm_running = false;
-            if (tpm_start(init_p->u.req.init_flags) < 0) {
-                res = TPM_FAIL;
+            if (tpm_start(init_p->u.req.init_flags, tpmversion, &res) < 0) {
                 logprintf(STDERR_FILENO,
                           "Error: Could not initialize the TPM.\n");
             } else {
-                res = TPM_SUCCESS;
                 tpm_running = true;
             }
             init_p->u.resp.tpm_result = res;
@@ -1005,7 +1102,7 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
 
         tpm_running = false;
 
-        TPM_Free(ptm_response);
+        free(ptm_response);
         ptm_response = NULL;
 
         fuse_reply_ioctl(req, 0, &res, sizeof(res));
@@ -1018,7 +1115,7 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
         res = TPM_SUCCESS;
         TPMLIB_Terminate();
 
-        TPM_Free(ptm_response);
+        free(ptm_response);
         ptm_response = NULL;
 
         fuse_reply_ioctl(req, 0, &res, sizeof(res));
@@ -1035,6 +1132,7 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
             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));
         }
@@ -1052,7 +1150,13 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
             if (re->u.req.loc > 4) {
                 res = TPM_BAD_LOCALITY;
             } else {
-                res = _TPM_IO_TpmEstablished_Reset(req, re->u.req.loc);
+                /* 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));
             }
         }
@@ -1064,7 +1168,9 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
             fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
         } else {
             ptm_loc *l = (ptm_loc *)in_buf;
-            if (l->u.req.loc > 4) {
+            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;
@@ -1119,7 +1225,7 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
          * execute in another thread that polls on a cancel
          * flag
          */
-        res = TPM_FAIL;
+        res = TPMLIB_CancelCommand();
         fuse_reply_ioctl(req, 0, &res, sizeof(res));
         break;
 
@@ -1176,6 +1282,66 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
         }
         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;
+
     default:
         fuse_reply_err(req, EINVAL);
     }
@@ -1185,18 +1351,29 @@ cleanup:
 
     if (exit_prg) {
         logprintf(STDOUT_FILENO, "CUSE TPM is shutting down.\n");
-        pidfile_remove();
-
-        exit(0);
+        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;
 }
 
@@ -1207,24 +1384,75 @@ static void ptm_init_done(void *userdata)
 
     /* at this point the entry in /dev/ is available */
     if (pidfile_write(getpid()) < 0) {
-        exit(-13);
+        ret = -13;
+        goto error_exit;
     }
 
     if (param->runas) {
         ret = change_process_owner(param->runas);
         if (ret)
-            exit(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)
 {
@@ -1241,27 +1469,47 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac
         {"name"          , required_argument, 0, 'n'},
         {"runas"         , 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
+        {"print-capabilities"
+                         ,       no_argument, 0, 'a'},
+        {"print-states"  ,       no_argument, 0, 'e'},
         {NULL            , 0                , 0, 0  },
     };
     struct cuse_info cinfo;
-    struct cuse_param param;
+    struct cuse_param param = {
+        .startupType = _TPM_ST_NONE,
+    };
     const char *devname = NULL;
-    char *cinfo_argv[1];
+    char *cinfo_argv[1] = { 0 };
     unsigned int num;
     struct passwd *passwd;
-    const char *tpmdir;
+    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));
 
+    log_set_prefix("swtpm: ");
+
+    tpmversion = TPMLIB_TPM_VERSION_1_2;
+
     while (true) {
         opt = getopt_long(argc, argv, "M:m:n:r:hv", longopts, &longindex);
 
@@ -1271,23 +1519,29 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac
         switch (opt) {
         case 'M': /* major */
             if (sscanf(optarg, "%u", &num) != 1) {
-                fprintf(stderr, "Could not parse major number\n");
-                return -1;
+                logprintf(STDERR_FILENO, "Could not parse major number\n");
+                ret = -1;
+                goto exit;
             }
             if (num > 65535) {
-                fprintf(stderr, "Major number outside valid range [0 - 65535]\n");
-                return -1;
+                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) {
-                fprintf(stderr, "Could not parse major number\n");
-                return -1;
+                logprintf(STDERR_FILENO, "Could not parse major number\n");
+                ret = -1;
+                goto exit;
             }
             if (num > 65535) {
-                fprintf(stderr, "Major number outside valid range [0 - 65535]\n");
-                return -1;
+                logprintf(STDERR_FILENO,
+                          "Major number outside valid range [0 - 65535]\n");
+                ret = -1;
+                goto exit;
             }
             cinfo.dev_minor = num;
             break;
@@ -1295,8 +1549,9 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac
             if (!cinfo.dev_info_argc) {
                 cinfo_argv[0] = calloc(1, strlen("DEVNAME=") + strlen(optarg) + 1);
                 if (!cinfo_argv[0]) {
-                    fprintf(stderr, "Out of memory\n");
-                    return -1;
+                    logprintf(STDERR_FILENO, "Out of memory\n");
+                    ret = -1;
+                    goto exit;
                 }
                 devname = optarg;
 
@@ -1325,80 +1580,181 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac
         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 'h': /* help */
             fprintf(stdout, usage, prgname, iface);
-            return 0;
+            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);
-            return 0;
+            goto exit;
         }
     }
 
-    if (!cinfo.dev_info_argv) {
-        fprintf(stderr, "Error: device name missing\n");
-        return -2;
+    if (optind < argc) {
+        logprintf(STDERR_FILENO,
+                  "Unknown parameter '%s'\n", argv[optind]);
+        ret = EXIT_FAILURE;
+        goto exit;
     }
 
-    if (handle_log_options(param.logging) < 0 ||
-        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)
-        return -3;
-
     if (setuid(0)) {
-        fprintf(stderr, "Error: Unable to setuid root. uid = %d, "
-                "euid = %d, gid = %d\n", getuid(), geteuid(), getgid());
-        return -4;
+        logprintf(STDERR_FILENO, "Error: Unable to setuid root. uid = %d, "
+                  "euid = %d, gid = %d\n", getuid(), geteuid(), getgid());
+        ret = -4;
+        goto exit;
     }
 
     if (param.runas) {
         if (!(passwd = getpwnam(param.runas))) {
-            fprintf(stderr, "User '%s' does not exist\n",
-                    param.runas);
-            return -5;
+            logprintf(STDERR_FILENO, "User '%s' does not exist\n",
+                      param.runas);
+            ret = -5;
+            goto exit;
         }
     }
 
-    tpmdir = tpmstate_get_dir();
-    if (tpmdir == NULL) {
-        fprintf(stderr,
-                "Error: No TPM state directory is defined; TPM_PATH is not set\n");
-        return -1;
+    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) < 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) {
-        fprintf(stderr,
-                "Error: Could not create device file name\n");
-        return -1;
+        logprintf(STDERR_FILENO,
+                  "Error: Could not create device file name\n");
+        ret = -1;
+        goto exit;
     }
     if (n >= (int)sizeof(path)) {
-        fprintf(stderr,
-                "Error: Buffer too small to create device file name\n");
-        return -1;
+        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);
-        fprintf(stderr,
-                "Error: A device '%s' already exists.\n",
-                path);
-        return -1;
+        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();
 
+    if (!need_init_cmd) {
+        if (tpm_start(0, tpmversion, &res) < 0) {
+            ret = -1;
+            goto exit;
+        }
+        tpm_running = true;
+    }
+
+    if (param.startupType != _TPM_ST_NONE) {
+        if (ptm_send_startup(param.startupType, tpmversion) < 0) {
+            ret = -1;
+            goto exit;
+        }
+    }
+
 #if GLIB_MINOR_VERSION >= 32
     g_mutex_init(FILE_OPS_LOCK);
 #else
     FILE_OPS_LOCK = g_mutex_new();
 #endif
 
-    return cuse_lowlevel_main(1, argv, &cinfo, &clops, &param);
+    ret = ptm_cuse_lowlevel_main(1, argv, &cinfo, &clops, &param);
+
+exit:
+    ptm_cleanup();
+    free(cinfo_argv[0]);
+
+    return ret;
 }