swtpm: Implement CMD_LOCK_STORAGE to lock storage

Implement CMD_LOCK_STORAGE / PTM_LOCK_STORAGE for a user to be able to
lock the storage of the storage backend (if supported) after its lock
has been released for example when the 'savestate' blob was received
while the TPM state was migrated.

Also adjust test case and extend man pages.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
diff --git a/include/swtpm/tpm_ioctl.h b/include/swtpm/tpm_ioctl.h
index 21be659..e506ef5 100644
--- a/include/swtpm/tpm_ioctl.h
+++ b/include/swtpm/tpm_ioctl.h
@@ -230,6 +230,20 @@
 #define SWTPM_INFO_TPMSPECIFICATION ((uint64_t)1 << 0)
 #define SWTPM_INFO_TPMATTRIBUTES    ((uint64_t)1 << 1)
 
+/*
+ * PTM_LOCK_STORAGE: Lock the storage and retry n times
+ */
+struct ptm_lockstorage {
+    union {
+        struct {
+            uint32_t retries; /* number of retries */
+        } req; /* request */
+        struct {
+            ptm_res tpm_result;
+        } resp; /* reponse */
+    } u;
+};
+
 typedef uint64_t ptm_cap;
 typedef struct ptm_est ptm_est;
 typedef struct ptm_reset_est ptm_reset_est;
@@ -241,6 +255,7 @@
 typedef struct ptm_getconfig ptm_getconfig;
 typedef struct ptm_setbuffersize ptm_setbuffersize;
 typedef struct ptm_getinfo ptm_getinfo;
+typedef struct ptm_lockstorage ptm_lockstorage;
 
 /* capability flags returned by PTM_GET_CAPABILITY */
 #define PTM_CAP_INIT               (1)
@@ -259,6 +274,7 @@
 #define PTM_CAP_SET_BUFFERSIZE     (1 << 13)
 #define PTM_CAP_GET_INFO           (1 << 14)
 #define PTM_CAP_SEND_COMMAND_HEADER (1 << 15)
+#define PTM_CAP_LOCK_STORAGE       (1 << 16)
 
 #ifndef _WIN32
 enum {
@@ -280,6 +296,7 @@
     PTM_SET_DATAFD         = _IOR('P', 15, ptm_res),
     PTM_SET_BUFFERSIZE     = _IOWR('P', 16, ptm_setbuffersize),
     PTM_GET_INFO           = _IOWR('P', 17, ptm_getinfo),
+    PTM_LOCK_STORAGE       = _IOWR('P', 18, ptm_lockstorage),
 };
 #endif
 
@@ -312,6 +329,7 @@
     CMD_SET_DATAFD,           /* 0x10 */
     CMD_SET_BUFFERSIZE,       /* 0x11 */
     CMD_GET_INFO,             /* 0x12 */
+    CMD_LOCK_STORAGE,         /* 0x13 */
 };
 
 #endif /* _TPM_IOCTL_H_ */
diff --git a/man/man3/swtpm_ioctls.pod b/man/man3/swtpm_ioctls.pod
index 54d0064..fa78a75 100644
--- a/man/man3/swtpm_ioctls.pod
+++ b/man/man3/swtpm_ioctls.pod
@@ -91,10 +91,14 @@
 The CMD_SET_DATAFD command is supported. This command only applies to UnixIO
 and there is no support for PTM_SET_DATAFD.
 
-=item B<PTM_SET_BUFFERSIZE (since v0.1)>
+=item B<PTM_CAP_SET_BUFFERSIZE (since v0.1)>
 
 The PTM_SET_BUFFERSIZE ioctl or CMD_SET_BUFFERSIZE command is supported.
 
+=item B<PTM_CAP_LOCK_STORAGE (since v0.8)>
+
+The PTM_LOCK_STORAGE ioctl or CMD_LOCK_STORAGE command is supported.
+
 =back
 
 =item B<PTM_INIT / CMD_INIT, ptm_init>
@@ -398,6 +402,28 @@
 The buffersize can only be changed when the TPM is stopped. The
 currently used buffersize can be read at any time.
 
+=item B<CMD_LOCK_STORAGE, ptm_lockstorage>
+
+Lock the storage and retry a given number of times with 10ms delay in between.
+Locking the storage may be necessary to do after the state of the TPM has been
+migrated out and the lock on the storage has been released when the 'savestate'
+blob was received and now the storage should be locked again.
+
+The ptm_lockstorage data structure looks as follows:
+
+ struct ptm_lockstorage {
+     union {
+         struct {
+             uint32_t retries; /* number of retries */
+         } req; /* request */
+         struct {
+             ptm_res tpm_result;
+         } resp; /* reponse */
+     } u;
+ };
+
+A TPM result code is returned in the tpm_result field.
+
 =back
 
 =head1 SEE ALSO
diff --git a/man/man8/swtpm_ioctl.pod b/man/man8/swtpm_ioctl.pod
index e3f64c0..18f1577 100644
--- a/man/man8/swtpm_ioctl.pod
+++ b/man/man8/swtpm_ioctl.pod
@@ -119,6 +119,12 @@
 I<TPMLIB_INFO_TPMATTRIBUTES>, which has the value 2, returns information
 about the manufacturer, model, and version of the TPM.
 
+=item B<--lock-storage E<lt>retriesE<gt>>
+
+Lock the storage and retry a given number of times with 10ms delay in between.
+Locking the storage may be necessary to do after the state of the TPM has been
+migrated out and the lock on the storage has been released when the 'savestate'
+blob was received and now the storage should be locked again.
 
 =back
 
diff --git a/src/swtpm/ctrlchannel.c b/src/swtpm/ctrlchannel.c
index 4eaf1d5..cbd62eb 100644
--- a/src/swtpm/ctrlchannel.c
+++ b/src/swtpm/ctrlchannel.c
@@ -456,7 +456,8 @@
             | PTM_CAP_SET_DATAFD
 #endif
             | PTM_CAP_SET_BUFFERSIZE
-            | PTM_CAP_GET_INFO;
+            | PTM_CAP_GET_INFO
+            | PTM_CAP_LOCK_STORAGE;
     if (tpmversion == TPMLIB_TPM_VERSION_2)
         caps |= PTM_CAP_SEND_COMMAND_HEADER;
 
@@ -520,6 +521,7 @@
     ptm_loc *pl;
     ptm_setbuffersize *psbs;
     ptm_getinfo *pgi, _pgi;
+    ptm_lockstorage *pls;
 
     size_t out_len = 0;
     TPM_RESULT res;
@@ -881,6 +883,24 @@
 
         break;
 
+    case CMD_LOCK_STORAGE:
+        if (n < (ssize_t)sizeof(pls->u.req)) /* rw */
+            goto err_bad_input;
+
+        pls = (ptm_lockstorage *)input.body;
+
+        mlp->locking_retries = be32toh(pls->u.req.retries);
+
+        pls = (ptm_lockstorage *)&output.body;
+        out_len = sizeof(pls->u.resp);
+
+        if (!mainloop_ensure_locked_storage(mlp))
+            pls->u.resp.tpm_result = htobe32(TPM_FAIL);
+        else
+            pls->u.resp.tpm_result = htobe32(TPM_SUCCESS);
+
+        break;
+
     default:
         logprintf(STDERR_FILENO,
                   "Error: Unknown command: 0x%08x\n", be32toh(input.cmd));
diff --git a/src/swtpm/cuse_tpm.c b/src/swtpm/cuse_tpm.c
index 5ea21cf..d979286 100644
--- a/src/swtpm/cuse_tpm.c
+++ b/src/swtpm/cuse_tpm.c
@@ -1088,6 +1088,7 @@
     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:
@@ -1129,7 +1130,8 @@
                     | PTM_CAP_STOP
                     | PTM_CAP_GET_CONFIG
                     | PTM_CAP_SET_BUFFERSIZE
-                    | PTM_CAP_GET_INFO;
+                    | PTM_CAP_GET_INFO
+                    | PTM_CAP_LOCK_STORAGE;
                 break;
             case TPMLIB_TPM_VERSION_1_2:
                 ptm_caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN
@@ -1144,7 +1146,8 @@
                     | PTM_CAP_STOP
                     | PTM_CAP_GET_CONFIG
                     | PTM_CAP_SET_BUFFERSIZE
-                    | PTM_CAP_GET_INFO;
+                    | PTM_CAP_GET_INFO
+                    | PTM_CAP_LOCK_STORAGE;
                 break;
             }
             fuse_reply_ioctl(req, 0, &ptm_caps, sizeof(ptm_caps));
@@ -1433,6 +1436,25 @@
         }
         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);
     }
diff --git a/src/swtpm_ioctl/tpm_ioctl.c b/src/swtpm_ioctl/tpm_ioctl.c
index 9eb785c..6c513ad 100644
--- a/src/swtpm_ioctl/tpm_ioctl.c
+++ b/src/swtpm_ioctl/tpm_ioctl.c
@@ -899,6 +899,8 @@
 "                        size; get minimum and maximum supported sizes\n"
 "--info <flags>        : get TPM implementation specific information;\n"
 "                        flags must be an integer value\n"
+"--lock-storage <n>    : lock the storage after it was unlocked; retry\n"
+"                        n times with 10ms delay in between\n"
 "--version             : display version and exit\n"
 "--help                : display help screen and exit\n"
 "\n"
@@ -917,6 +919,7 @@
     ptm_getconfig cfg;
     ptm_setbuffersize psbs;
     ptm_getinfo pgi;
+    ptm_lockstorage pls;
     char *tmp;
     size_t buffersize = 0;
     static struct option long_options[] = {
@@ -939,6 +942,7 @@
         {"load", required_argument, NULL, 'L'},
         {"version", no_argument, NULL, 'V'},
         {"info", required_argument, NULL, 'I'},
+        {"lock-storage", required_argument, NULL, 'o'},
         {"help", no_argument, NULL, 'H'},
         {NULL, 0, NULL, 0},
     };
@@ -950,6 +954,7 @@
     unsigned int locality = 0;
     unsigned int tpmbuffersize = 0;
     unsigned int tcp_port = 0;
+    unsigned int retries;
     bool is_chardev;
     unsigned long int info_flags = 0;
     char *endptr = NULL;
@@ -1042,6 +1047,14 @@
                 goto exit;
             }
             break;
+        case 'o':
+            command = argv[optind - 2];
+            if (sscanf(argv[optind - 1], "%u", &retries) != 1) {
+                fprintf(stderr, "Could not parse lock retries from %s.\n",
+                        argv[optind - 1]);
+                goto exit;
+            }
+            break;
         case 'V':
             versioninfo();
             ret = EXIT_SUCCESS;
@@ -1307,6 +1320,26 @@
             goto exit;
         }
         printf("%s\n", pgi.u.resp.buffer);
+    } else if (!strcmp(command, "--lock-storage")) {
+        memset(&pls, 0, sizeof(pls));
+        pls.u.req.retries = htodev32(is_chardev, 0);
+
+        n = ctrlcmd(fd, PTM_LOCK_STORAGE, &pls,
+                    sizeof(pls.u.req), sizeof(pls.u.resp));
+        if (n < 0) {
+            fprintf(stderr,
+                    "Could not execute PTM_LOCK_STORAGE: "
+                    "%s\n", strerror(errno));
+            goto exit;
+        }
+        res = devtoh32(is_chardev, pls.u.resp.tpm_result);
+        if (devtoh32(is_chardev, res) != 0) {
+            fprintf(stderr,
+                    "TPM result from PTM_LOCK_STORAGE: 0x%x\n",
+                    devtoh32(is_chardev, res));
+            goto exit;
+        }
+
     } else {
         usage(argv[0]);
         goto exit;
diff --git a/tests/test_clientfds.py b/tests/test_clientfds.py
index 9275a14..2bf24e9 100755
--- a/tests/test_clientfds.py
+++ b/tests/test_clientfds.py
@@ -70,7 +70,7 @@
     # test get capabilities
     # CMD_GET_CAPABILITY = 0x00 00 00 01
     cmd_get_caps = bytearray([0x00, 0x00, 0x00, 0x01])
-    expected_caps = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff])
+    expected_caps = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff])
 
     def toString(arr):
         return ' '.join('{:02x}'.format(x) for x in arr)
diff --git a/tests/test_ctrlchannel b/tests/test_ctrlchannel
index 509daeb..40e0bc8 100755
--- a/tests/test_ctrlchannel
+++ b/tests/test_ctrlchannel
@@ -99,9 +99,9 @@
 # Get the capability bits: CMD_GET_CAPABILITY = 0x00 00 00 01
 res="$(swtpm_ctrl_tx ${SWTPM_INTERFACE} '\x00\x00\x00\x01')"
 if [[ "$(uname -s)" =~ (Linux|OpenBSD|FreeBSD|NetBSD|Darwin|DragonFly) ]]; then
-	exp=" 00 00 00 00 00 00 7f ff"
+	exp=" 00 00 00 00 00 01 7f ff"
 else
-	exp=" 00 00 00 00 00 00 6f ff"
+	exp=" 00 00 00 00 00 01 6f ff"
 fi
 if [ "$res" != "$exp" ]; then
 	echo "Error: Unexpected response from CMD_GET_CAPABILITY:"
@@ -236,9 +236,9 @@
 # Get the capability bits: CMD_GET_CAPABILITY = 0x00 00 00 01
 res="$(swtpm_ctrl_tx ${SWTPM_INTERFACE} '\x00\x00\x00\x01')"
 if [[ "$(uname -s)" =~ (Linux|OpenBSD|FreeBSD|NetBSD|Darwin|DragonFly) ]]; then
-	exp=" 00 00 00 00 00 00 7f ff"
+	exp=" 00 00 00 00 00 01 7f ff"
 else
-	exp=" 00 00 00 00 00 00 6f ff"
+	exp=" 00 00 00 00 00 01 6f ff"
 fi
 if [ "$res" != "$exp" ]; then
 	echo "Error: Socket TPM: Unexpected response from CMD_GET_CAPABILITY:"
diff --git a/tests/test_ctrlchannel4 b/tests/test_ctrlchannel4
index 22eb7b2..f699923 100755
--- a/tests/test_ctrlchannel4
+++ b/tests/test_ctrlchannel4
@@ -49,7 +49,7 @@
 
 # Get the capability bits: CMD_GET_CAPABILITY = 0x00 00 00 01
 res="$(swtpm_ctrl_tx ${SWTPM_INTERFACE} '\x00\x00\x00\x01')"
-exp=" 00 00 00 00 00 00 7f ff"
+exp=" 00 00 00 00 00 01 7f ff"
 if [ "$res" != "$exp" ]; then
 	echo "Error: Unexpected response from CMD_GET_CAPABILITY:"
 	echo "       actual  : $res"