tpm2: add session handle context saving and restoring to the space code
authorJames Bottomley <James.Bottomley@HansenPartnership.com>
Tue, 31 Jan 2017 23:47:31 +0000 (15:47 -0800)
committerJarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Mon, 3 Apr 2017 19:46:02 +0000 (22:46 +0300)
Sessions are different from transient objects in that their handles
may not be virtualized (because they're used for some hmac
calculations).  Additionally when a session is context saved, a
vestigial memory remains in the TPM and if it is also flushed, that
will be lost and the session context will refuse to load next time, so
the code is updated to flush only transient objects after a context
save.  Add a separate array (chip->session_tbl) to save and restore
sessions by handle.  Use the failure of a context save or load to
signal that the session has been flushed from the TPM and we can
remove its memory from chip->session_tbl.

Sessions are also isolated during each instance of a tpm space.  This
means that spaces shouldn't be able to see each other's sessions and
is enforced by ensuring that a space user may only refer to sessions
handles that are present in their own chip->session_tbl.  Finally when
a space is closed, all the sessions belonging to it should be flushed
so the handles may be re-used by other spaces.

Note that if we get a session save or load error, all sessions are
effectively flushed.  Even though we restore the session buffer, all
the old sessions will refuse to load after the flush and they'll be
purged from our session memory.  This means that while transient
context handling is still soft in the face of errors, session handling
is hard (any failure of the model means all sessions are lost).

Fixes-from: Colin Ian King <colin.king@canonical.com>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Tested-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Reviewed-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
drivers/char/tpm/tpm-chip.c
drivers/char/tpm/tpm.h
drivers/char/tpm/tpm2-space.c
drivers/char/tpm/tpmrm-dev.c

index 187ec04ce9c31bef1b73d184dc3865c8e2ab37a2..aade6995f31065017a1a500ff8ab2223096c9245 100644 (file)
@@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
 
        kfree(chip->log.bios_event_log);
        kfree(chip->work_space.context_buf);
+       kfree(chip->work_space.session_buf);
        kfree(chip);
 }
 
@@ -224,6 +225,11 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
                rc = -ENOMEM;
                goto out;
        }
+       chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!chip->work_space.session_buf) {
+               rc = -ENOMEM;
+               goto out;
+       }
 
        return chip;
 
@@ -294,7 +300,6 @@ static int tpm_add_char_device(struct tpm_chip *chip)
                        "unable to cdev_add() %s, major %d, minor %d, err=%d\n",
                        dev_name(&chip->devs), MAJOR(chip->devs.devt),
                        MINOR(chip->devs.devt), rc);
-               tpm_del_char_device(chip, true);
                return rc;
        }
 
@@ -306,7 +311,6 @@ static int tpm_add_char_device(struct tpm_chip *chip)
                        dev_name(&chip->devs), MAJOR(chip->devs.devt),
                        MINOR(chip->devs.devt), rc);
                cdev_del(&chip->cdevs);
-               tpm_del_char_device(chip, true);
                return rc;
        }
 
index 16dd207a4542d4b9a13c95440a69254dc2abade9..5eacb3fd2ed26fd7c59b1067f44683526afa7b3f 100644 (file)
@@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
 struct tpm_space {
        u32 context_tbl[3];
        u8 *context_buf;
+       u32 session_tbl[3];
+       u8 *session_buf;
 };
 
 enum tpm_chip_flags {
@@ -588,7 +590,7 @@ unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
 int tpm2_probe(struct tpm_chip *chip);
 int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
 int tpm2_init_space(struct tpm_space *space);
-void tpm2_del_space(struct tpm_space *space);
+void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
 int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
                       u8 *cmd);
 int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
index e955548dbeed2f0793d8022c1864969be55dd2fa..e2e059d8ffec0c16ec9e106d6c848a036857295f 100644 (file)
@@ -32,18 +32,39 @@ struct tpm2_context {
        __be16 blob_size;
 } __packed;
 
+static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+               if (space->session_tbl[i])
+                       tpm2_flush_context_cmd(chip, space->session_tbl[i],
+                                              TPM_TRANSMIT_UNLOCKED);
+       }
+}
+
 int tpm2_init_space(struct tpm_space *space)
 {
        space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
        if (!space->context_buf)
                return -ENOMEM;
 
+       space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (space->session_buf == NULL) {
+               kfree(space->context_buf);
+               return -ENOMEM;
+       }
+
        return 0;
 }
 
-void tpm2_del_space(struct tpm_space *space)
+void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
 {
+       mutex_lock(&chip->tpm_mutex);
+       tpm2_flush_sessions(chip, space);
+       mutex_unlock(&chip->tpm_mutex);
        kfree(space->context_buf);
+       kfree(space->session_buf);
 }
 
 static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
@@ -69,6 +90,20 @@ static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
                         __func__, rc);
                tpm_buf_destroy(&tbuf);
                return -EFAULT;
+       } else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
+                  rc == TPM2_RC_REFERENCE_H0) {
+               /*
+                * TPM_RC_HANDLE means that the session context can't
+                * be loaded because of an internal counter mismatch
+                * that makes the TPM think there might have been a
+                * replay.  This might happen if the context was saved
+                * and loaded outside the space.
+                *
+                * TPM_RC_REFERENCE_H0 means the session has been
+                * flushed outside the space
+                */
+               rc = -ENOENT;
+               tpm_buf_destroy(&tbuf);
        } else if (rc > 0) {
                dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
                         __func__, rc);
@@ -121,7 +156,6 @@ static int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
        }
 
        memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
-       tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
        *offset += body_size;
        tpm_buf_destroy(&tbuf);
        return 0;
@@ -136,6 +170,8 @@ static void tpm2_flush_space(struct tpm_chip *chip)
                if (space->context_tbl[i] && ~space->context_tbl[i])
                        tpm2_flush_context_cmd(chip, space->context_tbl[i],
                                               TPM_TRANSMIT_UNLOCKED);
+
+       tpm2_flush_sessions(chip, space);
 }
 
 static int tpm2_load_space(struct tpm_chip *chip)
@@ -161,6 +197,28 @@ static int tpm2_load_space(struct tpm_chip *chip)
                        return rc;
        }
 
+       for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+               u32 handle;
+
+               if (!space->session_tbl[i])
+                       continue;
+
+               rc = tpm2_load_context(chip, space->session_buf,
+                                      &offset, &handle);
+               if (rc == -ENOENT) {
+                       /* load failed, just forget session */
+                       space->session_tbl[i] = 0;
+               } else if (rc) {
+                       tpm2_flush_space(chip);
+                       return rc;
+               }
+               if (handle != space->session_tbl[i]) {
+                       dev_warn(&chip->dev, "session restored to wrong handle\n");
+                       tpm2_flush_space(chip);
+                       return -EFAULT;
+               }
+       }
+
        return 0;
 }
 
@@ -171,7 +229,7 @@ static bool tpm2_map_to_phandle(struct tpm_space *space, void *handle)
        int i;
 
        i = 0xFFFFFF - (vhandle & 0xFFFFFF);
-       if (i > ARRAY_SIZE(space->context_tbl) || !space->context_tbl[i])
+       if (i >= ARRAY_SIZE(space->context_tbl) || !space->context_tbl[i])
                return false;
 
        phandle = space->context_tbl[i];
@@ -215,7 +273,10 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
 
        memcpy(&chip->work_space.context_tbl, &space->context_tbl,
               sizeof(space->context_tbl));
+       memcpy(&chip->work_space.session_tbl, &space->session_tbl,
+              sizeof(space->session_tbl));
        memcpy(chip->work_space.context_buf, space->context_buf, PAGE_SIZE);
+       memcpy(chip->work_space.session_buf, space->session_buf, PAGE_SIZE);
 
        rc = tpm2_load_space(chip);
        if (rc) {
@@ -232,6 +293,22 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
        return 0;
 }
 
+static bool tpm2_add_session(struct tpm_chip *chip, u32 handle)
+{
+       struct tpm_space *space = &chip->work_space;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++)
+               if (space->session_tbl[i] == 0)
+                       break;
+
+       if (i == ARRAY_SIZE(space->session_tbl))
+               return false;
+
+       space->session_tbl[i] = handle;
+       return true;
+}
+
 static u32 tpm2_map_to_vhandle(struct tpm_space *space, u32 phandle, bool alloc)
 {
        int i;
@@ -288,6 +365,8 @@ static int tpm2_map_response_header(struct tpm_chip *chip, u32 cc, u8 *rsp,
                break;
        case TPM2_HT_HMAC_SESSION:
        case TPM2_HT_POLICY_SESSION:
+               if (!tpm2_add_session(chip, phandle))
+                       goto out_no_slots;
                break;
        default:
                dev_err(&chip->dev, "%s: unknown handle 0x%08X\n",
@@ -350,15 +429,11 @@ static int tpm2_map_response_body(struct tpm_chip *chip, u32 cc, u8 *rsp,
                        data->handles[j] = cpu_to_be32(vhandle);
                        j++;
                        break;
-               case TPM2_HT_HMAC_SESSION:
-               case TPM2_HT_POLICY_SESSION:
+
+               default:
                        data->handles[j] = cpu_to_be32(phandle);
                        j++;
                        break;
-               default:
-                       dev_err(&chip->dev, "%s: unknown handle 0x%08X\n",
-                               __func__, phandle);
-                       break;
                }
 
        }
@@ -388,9 +463,28 @@ static int tpm2_save_space(struct tpm_chip *chip)
                } else if (rc)
                        return rc;
 
+               tpm2_flush_context_cmd(chip, space->context_tbl[i],
+                                      TPM_TRANSMIT_UNLOCKED);
                space->context_tbl[i] = ~0;
        }
 
+       for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+               if (!space->session_tbl[i])
+                       continue;
+
+               rc = tpm2_save_context(chip, space->session_tbl[i],
+                                      space->session_buf, PAGE_SIZE,
+                                      &offset);
+
+               if (rc == -ENOENT) {
+                       /* handle error saving session, just forget it */
+                       space->session_tbl[i] = 0;
+               } else if (rc < 0) {
+                       tpm2_flush_space(chip);
+                       return rc;
+               }
+       }
+
        return 0;
 }
 
@@ -425,7 +519,10 @@ int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
 
        memcpy(&space->context_tbl, &chip->work_space.context_tbl,
               sizeof(space->context_tbl));
+       memcpy(&space->session_tbl, &chip->work_space.session_tbl,
+              sizeof(space->session_tbl));
        memcpy(space->context_buf, chip->work_space.context_buf, PAGE_SIZE);
+       memcpy(space->session_buf, chip->work_space.session_buf, PAGE_SIZE);
 
        return 0;
 }
index 630bddce65a8f7a6b180e05bbd1e916ed72bd9a1..c636e7fdd1f5136e0eb50e7c8a72a6fbec2d8513 100644 (file)
@@ -39,7 +39,7 @@ static int tpmrm_release(struct inode *inode, struct file *file)
        struct tpmrm_priv *priv = container_of(fpriv, struct tpmrm_priv, priv);
 
        tpm_common_release(file, fpriv);
-       tpm2_del_space(&priv->space);
+       tpm2_del_space(fpriv->chip, &priv->space);
        kfree(priv);
 
        return 0;