[go: nahoru, domu]

tcti: add support for partial reads

This enables partial reads in tcti-device.
The feature has dependecy on the same being enable in the driver
so it is disabled by default. It can be enabled by a new configure
time option --enable-tcti-partial-reads=yes

By enabling this the Tss_ExecuteFinish function will call
Tss2_Tcti_Receive() twice. First with a NULL receive buffer, which
will cause the TCTI device to read only 10 bytes response header,
and return the actuall response size. Then the second call to
Tss2_Tcti_Receive() will read the remaining of the response.

NOTE: If this feature is not enabled in the driver then the first
read will cause the response by dropped after the first read and
the subsequent read will cause the connection to be closed and an
IO error returned.

Fixes: #1102

The driver support for partial reads has been added with commit:
8f82ffbc5b0b5e9a4546a2c8ab3366758ef76c62

Signed-off-by: Tadeusz Struk <tadeusz.struk@intel.com>
diff --git a/configure.ac b/configure.ac
index 4c2b900..7a90ed0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,15 @@
 AS_IF([test "x$enable_tcti_device_async" = "xyes"],
 	AC_DEFINE([TCTI_ASYNC],[1]))
 
+AC_ARG_ENABLE([tcti-partial-reads],
+    AS_HELP_STRING([--enable-tcti-partial-reads],
+	           [Enable partial reads for TCTI device
+		    (note: This needs to be supported by the kernel driver). default is no]),
+    [enable_tcti_partial_reads=$enableval],
+    [enable_tcti_partial_reads=no])
+AS_IF([test "x$enable_tcti_partial_reads" = "xyes"],
+	AC_DEFINE([TCTI_PARTIAL_READ],[1]))
+
 AC_ARG_WITH([crypto],
             [AS_HELP_STRING([--with-crypto={gcrypt,ossl}],
                             [sets the ESAPI crypto backend (default is OpenSSL)])],
@@ -319,5 +328,6 @@
     maxloglevel:        $with_maxloglevel
     doxygen:            $DX_FLAG_doc $enable_doxygen_doc
     tcti-device-async:  $enable_tcti_device_async
+    tcti-partial-read:  $enable_tcti_partial_reads
     crypto backend:     $with_crypto
 ])
diff --git a/src/tss2-sys/api/Tss2_Sys_Execute.c b/src/tss2-sys/api/Tss2_Sys_Execute.c
index d69c475..b29571c 100644
--- a/src/tss2-sys/api/Tss2_Sys_Execute.c
+++ b/src/tss2-sys/api/Tss2_Sys_Execute.c
@@ -47,8 +47,32 @@
     if (ctx->previousStage != CMD_STAGE_SEND_COMMAND)
         return TSS2_SYS_RC_BAD_SEQUENCE;
 
-    responseSize = ctx->maxCmdSize;
+#ifdef TCTI_PARTIAL_READ
+    /*
+     * First call receive with NULL as the response buffer to
+     * get the size of the response
+     */
+    rval = Tss2_Tcti_Receive(ctx->tctiContext, &responseSize,
+                             NULL, timeout);
+    if (rval)
+        return rval;
 
+    if (responseSize < sizeof(TPM20_Header_Out)) {
+        ctx->previousStage = CMD_STAGE_PREPARE;
+        return TSS2_SYS_RC_INSUFFICIENT_RESPONSE;
+    }
+    if (responseSize > ctx->maxCmdSize) {
+        ctx->previousStage = CMD_STAGE_PREPARE;
+        return TSS2_SYS_RC_INSUFFICIENT_CONTEXT;
+    }
+#else
+    /* For none partial reads set the size to maxCmdSize */
+    responseSize = ctx->maxCmdSize;
+#endif
+
+    /*
+     * Then call receive again with the response buffer to read the response
+     */
     rval = Tss2_Tcti_Receive(ctx->tctiContext, &responseSize,
                              ctx->cmdBuffer, timeout);
     if (rval == TSS2_TCTI_RC_INSUFFICIENT_BUFFER)
diff --git a/src/tss2-tcti/tcti-common.h b/src/tss2-tcti/tcti-common.h
index f7ec09c..a6beaea 100644
--- a/src/tss2-tcti/tcti-common.h
+++ b/src/tss2-tcti/tcti-common.h
@@ -57,6 +57,7 @@
     tcti_state_t state;
     tpm_header_t header;
     uint8_t locality;
+    bool partial;
 } TSS2_TCTI_COMMON_CONTEXT;
 
 /*
diff --git a/src/tss2-tcti/tcti-device.c b/src/tss2-tcti/tcti-device.c
index 5b5da40..c91ad83 100644
--- a/src/tss2-tcti/tcti-device.c
+++ b/src/tss2-tcti/tcti-device.c
@@ -17,7 +17,7 @@
 
 #include "tss2_tcti.h"
 #include "tss2_tcti_device.h"
-
+#include "tss2_mu.h"
 #include "tcti-common.h"
 #include "tcti-device.h"
 #include "util/io.h"
@@ -122,6 +122,11 @@
     ssize_t size = 0;
     struct pollfd fds;
     int rc_poll, nfds = 1;
+#ifdef TCTI_PARTIAL_READ
+    uint8_t header[TPM_HEADER_SIZE];
+    size_t offset = 2;
+    UINT32 partial_size;
+#endif
 
     if (tcti_dev == NULL) {
         return TSS2_TCTI_RC_BAD_CONTEXT;
@@ -144,19 +149,80 @@
     }
 #endif
     if (response_buffer == NULL) {
+#ifndef TCTI_PARTIAL_READ
         LOG_DEBUG ("Caller queried for size but linux kernel doesn't allow this. "
                    "Returning 4k which is the max size for a response buffer.");
         *response_size = 4096;
         return TSS2_RC_SUCCESS;
     }
+#else
+        /* Read the header only and get the response size out of it */
+        LOG_DEBUG("Partial read - reading response size");
+        fds.fd = tcti_dev->fd;
+        fds.events = POLLIN;
+
+        rc_poll = poll(&fds, nfds, timeout);
+        if (rc_poll < 0) {
+            LOG_ERROR ("Failed to poll for response from fd %d, got errno %d: %s",
+                       tcti_dev->fd, errno, strerror (errno));
+            return TSS2_TCTI_RC_IO_ERROR;
+        } else if (rc_poll == 0) {
+            LOG_INFO ("Poll timed out on fd %d.", tcti_dev->fd);
+            return TSS2_TCTI_RC_TRY_AGAIN;
+        } else if (fds.revents == POLLIN) {
+            TEMP_RETRY (size, read (tcti_dev->fd, header, TPM_HEADER_SIZE));
+            if (size < 0 || size != TPM_HEADER_SIZE) {
+                LOG_ERROR ("Failed to get response size fd %d, got errno %d: %s",
+                       tcti_dev->fd, errno, strerror (errno));
+                return TSS2_TCTI_RC_IO_ERROR;
+            }
+        }
+        LOG_DEBUG("Partial read - received header");
+            rc = Tss2_MU_UINT32_Unmarshal(header, TPM_HEADER_SIZE,
+                                          &offset, &partial_size);
+        if (rc != TSS2_RC_SUCCESS) {
+            LOG_ERROR ("Failed to unmarshal response size.");
+            return rc;
+        }
+        if (partial_size < TPM_HEADER_SIZE) {
+            LOG_ERROR ("Received %zu bytes, not enough to hold a TPM2 response "
+                       "header.", size);
+            return TSS2_TCTI_RC_GENERAL_FAILURE;
+        }
+
+        LOG_DEBUG("Partial read - received response size %d.", partial_size);
+        tcti_common->partial = true;
+        *response_size = partial_size;
+        memcpy(&tcti_common->header, header, TPM_HEADER_SIZE);
+        return rc;
+    }
+#endif
+
+#ifndef TCTI_PARTIAL_READ
     if (*response_size < 4096) {
+#else
+    if (*response_size < TPM_HEADER_SIZE) {
+#endif
         LOG_INFO ("Caller provided buffer that *may* not be large enough to "
                   "hold the response buffer.");
     }
+
+    /* In case when the whole response is just the 10 bytes header
+     * and we have read it already to get the size, we don't need
+     * to call poll and read again. Just copy what we have read
+     * and return.
+     */
+    if (tcti_common->partial == true && *response_size == TPM_HEADER_SIZE) {
+        memcpy(response_buffer, &tcti_common->header, TPM_HEADER_SIZE);
+        tcti_common->partial = false;
+        goto out;
+    }
+
     /*
-     * The kernel driver will only return a response buffer in a single read
-     * operation. If we try to read again before sending another command
+     * The older kernel driver will only return a response buffer in a single
+     * read operation. If we try to read again before sending another command
      * the kernel will close the file descriptor and we'll get an EOF.
+     * Newer kernels should have partial reads enabled.
      */
     fds.fd = tcti_dev->fd;
     fds.events = POLLIN;
@@ -170,7 +236,14 @@
         LOG_INFO ("Poll timed out on fd %d.", tcti_dev->fd);
         return TSS2_TCTI_RC_TRY_AGAIN;
     } else if (fds.revents == POLLIN) {
-        TEMP_RETRY (size, read(tcti_dev->fd, response_buffer, *response_size));
+        if (tcti_common->partial == true) {
+            memcpy(response_buffer, &tcti_common->header, TPM_HEADER_SIZE);
+            TEMP_RETRY (size, read (tcti_dev->fd, response_buffer +
+                        TPM_HEADER_SIZE, *response_size - TPM_HEADER_SIZE));
+        } else {
+            TEMP_RETRY (size, read (tcti_dev->fd, response_buffer,
+                        *response_size));
+        }
         if (size < 0) {
             LOG_ERROR ("Failed to read response from fd %d, got errno %d: %s",
                tcti_dev->fd, errno, strerror (errno));
@@ -182,24 +255,31 @@
         rc = TSS2_TCTI_RC_NO_CONNECTION;
         goto out;
     }
+
+    size += tcti_common->partial ? TPM_HEADER_SIZE : 0;
     LOGBLOB_DEBUG(response_buffer, size, "Response Received");
-    if (size < (ssize_t)TPM_HEADER_SIZE) {
+    tcti_common->partial = false;
+
+    if ((size_t)size < TPM_HEADER_SIZE) {
         LOG_ERROR ("Received %zu bytes, not enough to hold a TPM2 response "
                    "header.", size);
         rc = TSS2_TCTI_RC_GENERAL_FAILURE;
         goto out;
     }
+
     rc = header_unmarshal (response_buffer, &tcti_common->header);
-    if (rc != TSS2_RC_SUCCESS) {
+    if (rc != TSS2_RC_SUCCESS)
         goto out;
-    }
+
+    LOG_DEBUG("Size from header %u bytes read %zu", tcti_common->header.size, size);
+
     if ((size_t)size != tcti_common->header.size) {
-        LOG_WARNING ("TPM2 header size disagrees with number of bytes read "
+        LOG_WARNING ("TPM2 response size disagrees with number of bytes read "
                      "from fd %d. Header says %u but we read %zu bytes.",
                      tcti_dev->fd, tcti_common->header.size, size);
     }
     if (*response_size < tcti_common->header.size) {
-        LOG_WARNING ("TPM2 response header size is larger than the provided "
+        LOG_WARNING ("TPM2 response size is larger than the provided "
                      "buffer: future use of this TCTI will likely fail.");
         rc = TSS2_TCTI_RC_GENERAL_FAILURE;
     }
diff --git a/test/integration/main-esapi.c b/test/integration/main-esapi.c
index 8e87427..1886357 100644
--- a/test/integration/main-esapi.c
+++ b/test/integration/main-esapi.c
@@ -98,10 +98,11 @@
 
     if (tcti_proxy->state == intercepting) {
         *response_size = sizeof(yielded_response);
-        if (response_buffer != NULL)
-            memcpy(response_buffer, &yielded_response[0], sizeof(yielded_response));
 
-        tcti_proxy->state = forwarding;
+        if (response_buffer != NULL) {
+            memcpy(response_buffer, &yielded_response[0], sizeof(yielded_response));
+            tcti_proxy->state = forwarding;
+        }
         return TSS2_RC_SUCCESS;
     }
 
@@ -112,7 +113,10 @@
         return rval;
     }
 
-    tcti_proxy->state = intercepting;
+    /* First read with response buffer == NULL is to get the size of the
+     * response. The subsequent read needs to be forwarded also */
+    if (response_buffer != NULL)
+        tcti_proxy->state = intercepting;
 
     return rval;
 }
diff --git a/test/unit/io.c b/test/unit/io.c
index e95592a..855eb63 100644
--- a/test/unit/io.c
+++ b/test/unit/io.c
@@ -97,10 +97,11 @@
 read_all_twice_eof (void **state)
 {
     ssize_t ret;
+    uint8_t buf [10];
 
     will_return (__wrap_read, 5);
     will_return (__wrap_read, 0);
-    ret = read_all (10, NULL, 10);
+    ret = read_all (10, buf, 10);
     assert_int_equal (ret, 5);
 }
 /* When passed all NULL values ensure that we get back the expected RC. */