Session handles are slightly more difficult to manage because any TPM
only has a finite number of allowed handles, even if the session has
been saved; so when you context save a session, you must not flush it
because that would destroy the ability to context load it (you only
flush sessions when you're done with them) and the tpm won't re-use
the handle.  Additionally, sessions can be flushed as part of the
successful execution of a command if the continueSession attribute is
clear, so we have to mark any session we find in the command (using
TPM2_HT_TAG_FOR_FLUSH) so it can be cleared from the space if the
command successfully executes.

Finally, a session may also be cleared by flushing it, so we have to
emulate the TPM2_FlushContext command to see if a session is being
flushed and manually clear it from the space.

We also fully flush all sessions on device close.

Signed-off-by: James Bottomley <james.bottom...@hansenpartnership.com>

diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index f5c9355..d8e896e 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -400,6 +400,10 @@ ssize_t tpm_transmit(struct tpm_chip *chip, struct 
tpm_space *space,
        if (chip->dev.parent)
                pm_runtime_get_sync(chip->dev.parent);
 
+       rc = tpm2_emulate(chip, space, ordinal, buf, bufsiz);
+       if (rc)
+               goto out;
+
        rc = tpm2_prepare_space(chip, space, ordinal, buf, bufsiz);
        if (rc)
                goto out;
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index adf7810..b922652 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -136,7 +136,8 @@ enum tpm2_capabilities {
 };
 
 enum tpm2_properties {
-       TPM_PT_TOTAL_COMMANDS   = 0x0129,
+       TPM_PT_ACTIVE_SESSIONS_MAX      = 0x0111,
+       TPM_PT_TOTAL_COMMANDS           = 0x0129,
 };
 
 enum tpm2_startup_types {
@@ -214,6 +215,8 @@ struct tpm_chip {
        struct tpm_space *work_space;
        u32 nr_commands;
        u32 *cc_attrs_tbl;
+       struct tpm_sessions *sessions;
+       int max_sessions;
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -583,4 +586,7 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct 
tpm_space *space,
                       u32 cc, u8 *buf, size_t bufsiz);
 int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
                      u32 cc, u8 *buf, size_t bufsiz);
+void tpm2_flush_space(struct tpm_chip *chip, struct tpm_space *space);
+int tpm2_emulate(struct tpm_chip *chip, struct tpm_space *space,
+                 u32 cc, u8 *buf, size_t bufsiz);
 #endif
diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
index 285361e..e8e9f32 100644
--- a/drivers/char/tpm/tpm2-space.c
+++ b/drivers/char/tpm/tpm2-space.c
@@ -25,15 +25,29 @@ enum tpm2_handle_types {
        TPM2_HT_TRANSIENT       = 0x80000000,
 };
 
-static void tpm2_flush_space(struct tpm_chip *chip)
+#define TPM2_HT_TAG_FOR_FLUSH  0xF0000000
+
+void tpm2_flush_space(struct tpm_chip *chip, struct tpm_space *space)
 {
-       struct tpm_space *space = chip->work_space;
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++)
-               if (space->context_tbl[i] && ~space->context_tbl[i])
-                       tpm2_flush_context_cmd(chip, space->context_tbl[i],
-                                              TPM_TRANSMIT_UNLOCKED);
+       for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
+               u32 handle = space->context_tbl[i];
+               u32 handle_type;
+
+               if (handle == 0 || handle == ~0)
+                       continue;
+
+               if ((handle & TPM2_HT_TAG_FOR_FLUSH) ==
+                   TPM2_HT_TAG_FOR_FLUSH)
+                       handle &= ~TPM2_HT_TAG_FOR_FLUSH;
+
+               handle_type = (handle & 0xFF000000);
+
+               tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
+
+               space->context_tbl[i] = 0;
+       }
 }
 
 struct tpm2_context {
@@ -54,15 +68,11 @@ static int tpm2_load_space(struct tpm_chip *chip)
        u32 s;
 
        for (i = 0, j = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
+               u32 phandle, phandle_type;
+
                if (!space->context_tbl[i])
                        continue;
 
-               /* sanity check, should never happen */
-               if (~space->context_tbl[i]) {
-                       dev_err(&chip->dev, "context table is inconsistent");
-                       return -EFAULT;
-               }
-
                rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS,
                                 TPM2_CC_CONTEXT_LOAD);
                if (rc)
@@ -80,9 +90,23 @@ static int tpm2_load_space(struct tpm_chip *chip)
                        rc = -EFAULT;
                        goto out_err;
                }
+               phandle = get_unaligned_be32((__be32 
*)&buf.data[TPM_HEADER_SIZE]);
+               phandle_type = (phandle & 0xFF000000);
+               if (phandle_type == TPM2_HT_TRANSIENT &&
+                   space->context_tbl[i] != ~0) {
+                       dev_err(&chip->dev, "context table is inconsistent");
+                       rc = -EFAULT;
+                       goto out_err;
+               }
+               if ((phandle_type == TPM2_HT_HMAC_SESSION ||
+                    phandle_type == TPM2_HT_POLICY_SESSION) &&
+                   space->context_tbl[i] != phandle) {
+                       dev_err(&chip->dev, "session handle changed\n");
+                       rc = -EFAULT;
+                       goto out_err;
+               }
 
-               space->context_tbl[i] =
-                       be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE]);
+               space->context_tbl[i] = phandle;
 
                j += s;
 
@@ -93,10 +117,85 @@ static int tpm2_load_space(struct tpm_chip *chip)
 
 out_err:
        tpm_buf_destroy(&buf);
-       tpm2_flush_space(chip);
+       tpm2_flush_space(chip, space);
        return rc;
 }
 
+static void tpm2_unmap_sessions(struct tpm_chip *chip, u32 rc)
+{
+       struct tpm_space *space = chip->work_space;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
+               if ((space->context_tbl[i] & TPM2_HT_TAG_FOR_FLUSH) !=
+                   TPM2_HT_TAG_FOR_FLUSH)
+                       continue;
+               if (rc == TPM2_RC_SUCCESS)
+                       space->context_tbl[i] = 0;
+               else
+                       /* for unsuccessful command, keep session */
+                       space->context_tbl[i] &= ~TPM2_HT_TAG_FOR_FLUSH;
+       }
+}
+
+static int tpm2_map_sessions(struct tpm_chip *chip, u8 *buf, size_t len,
+                            size_t start)
+{
+       struct tpm_space *space = chip->work_space;
+       u32 size = be32_to_cpup((__be32 *)&buf[start]);
+       int i;
+
+       /* skip over authorizationSize */
+       start += 4;
+
+       if (size > len - start) {
+               dev_err(&chip->dev, "Invalid authorization header size %u\n",
+                       size);
+               return -EINVAL;
+       }
+
+       for (i = start; i < start+size; ) {
+               u16 skip;
+               __be32 *handlep;
+               u8 attr;
+               int j;
+               u32 handle_type;
+
+               /* TPMI_SH_AUTH_SESSION */
+               handlep = (__be32 *)&buf[i];
+               handle_type = get_unaligned_be32(handlep) & 0xFF000000;
+               i += 4;
+               /* TPM2B_DIGEST */
+               skip = get_unaligned_be16((__be16 *)&buf[i]);
+               i += skip + sizeof(skip);
+               /* TPMA_SESSION */
+               attr = buf[i++];
+               /* TPM2B_AUTH */
+               skip = get_unaligned_be16((__be16 *)&buf[i]);
+               i += skip + sizeof(skip);
+
+               if (handle_type != TPM2_HT_HMAC_SESSION &&
+                   handle_type != TPM2_HT_POLICY_SESSION)
+                       continue;
+
+               j = 0xFFFFFF - (get_unaligned_be32(handlep) & 0xFFFFFF);
+               if (j > ARRAY_SIZE(space->context_tbl) ||
+                   !space->context_tbl[j])
+                       return -EINVAL;
+               put_unaligned_be32(space->context_tbl[j], handlep);
+               if ((attr & 1) == 0)
+                       /* session is flushed by the command */
+                       space->context_tbl[j] |= TPM2_HT_TAG_FOR_FLUSH;
+       }
+
+       if (i != start+size) {
+               dev_err(&chip->dev, "Authorization session overflow\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd, size_t len)
 {
        struct tpm_space *space = chip->work_space;
@@ -104,6 +203,7 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 cc, 
u8 *cmd, size_t len)
        u32 vhandle;
        u32 phandle;
        u32 attrs;
+       u16 tag = get_unaligned_be16((__be16 *)cmd);
        int i;
        int j;
        int rc;
@@ -131,11 +231,14 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 
cc, u8 *cmd, size_t len)
                *((__be32 *)&cmd[TPM_HEADER_SIZE + 4 * i]) =
                        cpu_to_be32(phandle);
        }
+       if (tag == TPM2_ST_SESSIONS)
+               tpm2_map_sessions(chip, cmd, len,
+                                 TPM_HEADER_SIZE + 4*nr_handles);
 
        return 0;
 
 out_err:
-       tpm2_flush_space(chip);
+       tpm2_flush_space(chip, space);
        return rc;
 }
 
@@ -163,13 +266,17 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct 
tpm_space *space,
 static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t 
len)
 {
        struct tpm_space *space = chip->work_space;
-       u32 phandle;
+       u32 phandle, phandle_type;
        u32 vhandle;
        u32 attrs;
        u32 return_code = get_unaligned_be32((__be32 *)&rsp[6]);
+       u16 tag = get_unaligned_be16((__be16 *)rsp);
        int i;
        int rc;
 
+       if (tag == TPM2_ST_SESSIONS)
+               tpm2_unmap_sessions(chip, return_code);
+
        if (return_code != TPM2_RC_SUCCESS)
                return 0;
 
@@ -185,7 +292,10 @@ static int tpm2_map_response(struct tpm_chip *chip, u32 
cc, u8 *rsp, size_t len)
                return 0;
 
        phandle = be32_to_cpup((__be32 *)&rsp[TPM_HEADER_SIZE]);
-       if ((phandle & 0xFF000000) != TPM2_HT_TRANSIENT)
+       phandle_type = (phandle & 0xFF000000);
+       if (phandle_type != TPM2_HT_TRANSIENT &&
+           phandle_type != TPM2_HT_HMAC_SESSION &&
+           phandle_type != TPM2_HT_POLICY_SESSION)
                return 0;
 
        /* Garbage collect a dead context. */
@@ -208,13 +318,13 @@ static int tpm2_map_response(struct tpm_chip *chip, u32 
cc, u8 *rsp, size_t len)
        }
 
        space->context_tbl[i] = phandle;
-       vhandle = TPM2_HT_TRANSIENT | (0xFFFFFF - i);
+       vhandle = phandle_type | (0xFFFFFF - i);
        *(__be32 *)&rsp[TPM_HEADER_SIZE] = cpu_to_be32(vhandle);
 
        return 0;
 
 out_err:
-       tpm2_flush_space(chip);
+       tpm2_flush_space(chip, space);
        return rc;
 }
 
@@ -228,9 +338,20 @@ static int tpm2_save_space(struct tpm_chip *chip)
        u32 s;
 
        for (i = 0, j = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
-               if (!(space->context_tbl[i] && ~space->context_tbl[i]))
+               u32 phandle, phandle_type;
+
+               phandle = space->context_tbl[i];
+
+               if (phandle == 0)
                        continue;
 
+               if (phandle == ~0) {
+                       dev_err(&chip->dev, "context table is inconsistent\n");
+                       return -EFAULT;
+               }
+
+               phandle_type = (phandle & 0xFF000000);
+
                rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS,
                                  TPM2_CC_CONTEXT_SAVE);
                if (rc)
@@ -260,10 +381,11 @@ static int tpm2_save_space(struct tpm_chip *chip)
 
                memcpy(&space->context_buf[j], &buf.data[TPM_HEADER_SIZE], s);
 
-               tpm2_flush_context_cmd(chip, space->context_tbl[i],
-                                      TPM_TRANSMIT_UNLOCKED);
-
-               space->context_tbl[i] = ~0;
+               if (phandle_type == TPM2_HT_TRANSIENT) {
+                       tpm2_flush_context_cmd(chip, space->context_tbl[i],
+                                              TPM_TRANSMIT_UNLOCKED);
+                       space->context_tbl[i] = ~0;
+               }
 
                j += s;
 
@@ -273,7 +395,7 @@ static int tpm2_save_space(struct tpm_chip *chip)
        return 0;
 out_err:
        tpm_buf_destroy(&buf);
-       tpm2_flush_space(chip);
+       tpm2_flush_space(chip, space);
        return rc;
 }
 
@@ -297,3 +419,60 @@ int tpm2_commit_space(struct tpm_chip *chip, struct 
tpm_space *space,
 
        return 0;
 }
+
+/* if a space is active, emulate some commands */
+int tpm2_emulate(struct tpm_chip *chip, struct tpm_space *space,
+                u32 cc, u8 *buf, size_t bufsiz)
+{
+       int i, j, k;
+       u32 vhandle, handle_type, phandle;
+       struct tpm2_context *ctx;
+       static struct tpm_output_header buf_rc = {
+               .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS),
+               .length = cpu_to_be32(sizeof(struct tpm_output_header)),
+       };
+
+       if (!space)
+               return 0;
+
+       if (cc != TPM2_CC_FLUSH_CONTEXT)
+               return 0;
+       vhandle = get_unaligned_be32((__be32 *)&buf[10]);
+       handle_type = (vhandle & 0xFF000000);
+
+       if (handle_type != TPM2_HT_HMAC_SESSION &&
+           handle_type != TPM2_HT_POLICY_SESSION &&
+           handle_type != TPM2_HT_TRANSIENT)
+               /* let the TPM figure out and return the error */
+               return 0;
+
+       buf_rc.return_code = TPM2_RC_HANDLE;
+
+       j = 0xFFFFFF - (vhandle & 0xFFFFFF);
+       if (j > ARRAY_SIZE(space->context_tbl))
+               goto error;
+       phandle = space->context_tbl[j];
+       if (phandle != ~0)
+               goto error;
+
+       for (i = 0, k = 0; i <= j; i++) {
+               ctx = (struct tpm2_context *)&space->context_buf[k];
+
+               if (space->context_tbl[i] == 0)
+                       continue;
+
+               k += sizeof(*ctx) + get_unaligned_be16(&ctx->blob_size);
+       }
+       /* move all the contexts up */
+       memcpy(ctx, &space->context_buf[k], PAGE_SIZE - k);
+       space->context_tbl[j] = 0;
+
+       if (handle_type != TPM2_HT_TRANSIENT)
+               tpm2_flush_context_cmd(chip, phandle, TPM_TRANSMIT_UNLOCKED);
+
+       buf_rc.return_code = TPM2_RC_SUCCESS;
+
+ error:
+       memcpy(buf, &buf_rc, sizeof(buf_rc));
+       return sizeof(buf_rc);
+}
diff --git a/drivers/char/tpm/tpms-dev.c b/drivers/char/tpm/tpms-dev.c
index c10b308..3eb5955 100644
--- a/drivers/char/tpm/tpms-dev.c
+++ b/drivers/char/tpm/tpms-dev.c
@@ -36,6 +36,7 @@ static int tpms_release(struct inode *inode, struct file 
*file)
        struct file_priv *fpriv = file->private_data;
        struct tpms_priv *priv = container_of(fpriv, struct tpms_priv, priv);
 
+       tpm2_flush_space(fpriv->chip, &priv->space);
        tpm_common_release(file, fpriv);
        kfree(priv);
 

Reply via email to