Re: [PATCH v2 03/10] security: keys: trusted: Include TPM2 creation data

Kees Cook
2022-09-20T23:04:39Z

On Tue, Aug 23, 2022 at 03:25:19PM -0700, Evan Green wrote:
> In addition to the private key and public key, the TPM2_Create
> command may also return creation data, a creation hash, and a creation
> ticket. These fields allow the TPM to attest to the contents of a
> specified set of PCRs at the time the trusted key was created. Encrypted
> hibernation will use this to ensure that PCRs settable only by the
> kernel were set properly at the time of creation, indicating this is an
> authentic hibernate key.
>
> Encode these additional parameters into the ASN.1 created to represent
> the key blob. The new fields are made optional so that they don't bloat
> key blobs which don't need them, and to ensure interoperability with
> old blobs.
>
> ---
>
> (no changes since v1)
>
> This is a replacement for Matthew's original patch here:
> https://patchwork.kernel.org/patch/12096489/
>
> That patch was written before the exported key format was switched to
> ASN.1. This patch accomplishes the same thing (saving, loading, and
> getting pointers to the creation data) while utilizing the new ASN.1
> format.

This part (between your S-o-b and the "---") should got below the "---"
after your S-o-b, otherwise tooling will include it in the commit log
(or lose your S-o-b).

>
> Signed-off-by: Evan Green <evgreen@chromium.org>
> ---
> include/keys/trusted-type.h | 8 +
> security/keys/trusted-keys/tpm2key.asn1 | 5 +-
> security/keys/trusted-keys/trusted_tpm2.c | 202 +++++++++++++++++++---
> 3 files changed, 190 insertions(+), 25 deletions(-)
>
> diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
> index 4eb64548a74f1a..209086fed240a5 100644
> --- a/include/keys/trusted-type.h
> +++ b/include/keys/trusted-type.h
> @@ -22,15 +22,23 @@
> #define MAX_BLOB_SIZE 512
> #define MAX_PCRINFO_SIZE 64
> #define MAX_DIGEST_SIZE 64
> +#define MAX_CREATION_DATA 412
> +#define MAX_TK 76
>
> struct trusted_key_payload {
> struct rcu_head rcu;
> unsigned int key_len;
> unsigned int blob_len;
> + unsigned int creation_len;
> + unsigned int creation_hash_len;
> + unsigned int tk_len;
> unsigned char migratable;
> unsigned char old_format;
> unsigned char key[MAX_KEY_SIZE + 1];
> unsigned char blob[MAX_BLOB_SIZE];
> + unsigned char *creation;
> + unsigned char *creation_hash;
> + unsigned char *tk;
> };
>
> struct trusted_key_options {
> diff --git a/security/keys/trusted-keys/tpm2key.asn1 b/security/keys/trusted-keys/tpm2key.asn1
> index f57f869ad60068..1bfbf290e523a3 100644
> --- a/security/keys/trusted-keys/tpm2key.asn1
> +++ b/security/keys/trusted-keys/tpm2key.asn1
> @@ -7,5 +7,8 @@ TPMKey ::= SEQUENCE {
> emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL,
> parent INTEGER ({tpm2_key_parent}),
> pubkey OCTET STRING ({tpm2_key_pub}),
> - privkey OCTET STRING ({tpm2_key_priv})
> + privkey OCTET STRING ({tpm2_key_priv}),
> + creationData [1] EXPLICIT OCTET STRING OPTIONAL ({tpm2_key_creation_data}),
> + creationHash [2] EXPLICIT OCTET STRING OPTIONAL ({tpm2_key_creation_hash}),
> + creationTk [3] EXPLICIT OCTET STRING OPTIONAL ({tpm2_key_creation_tk})
> }

Maybe include a link (or named reference) to these fields from the TPM
spec?

> [...]
> @@ -46,6 +49,26 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
>
> pub_len = get_unaligned_be16(src) + 2;
> pub = src;
> + src += pub_len;
> +
> + creation_data_len = get_unaligned_be16(src);
> + if (creation_data_len) {
> + creation_data_len += 2;
> + creation_data = src;
> + src += creation_data_len;
> +
> + creation_hash_len = get_unaligned_be16(src) + 2;
> + creation_hash = src;
> + src += creation_hash_len;
> +
> + /*
> + * The creation ticket (TPMT_TK_CREATION) consists of a 2 byte
> + * tag, 4 byte handle, and then a TPM2B_DIGEST, which is a 2
> + * byte length followed by data.
> + */
> + creation_tk_len = get_unaligned_be16(src + 6) + 8;
> + creation_tk = src;
> + }
>
> if (!scratch)
> return -ENOMEM;

I don't see anything in this code (even before your patch) actually
checking length against the "len" argument to tpm2_key_encode(). I think
that needs to be fixed so proper bounds checking can be done here.
Otherwise how do we know if we're running off the end of "src"?

Yes, I realize if we have a malicious TPM everything goes out the
window, but TPMs don't always behave -- this code should likely be more
defensive. Or, I've misunderstood where "src" is coming from.
Regardless, my question stands: what is checking "len"?

> @@ -63,26 +86,81 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
> }
>
> /*
> - * Assume both octet strings will encode to a 2 byte definite length
> + * Assume each octet string will encode to a 2 byte definite length.
> + * Each optional octet string consumes one extra byte.
> *
> - * Note: For a well behaved TPM, this warning should never
> - * trigger, so if it does there's something nefarious going on
> + * Note: For a well behaved TPM, this warning should never trigger, so
> + * if it does there's something nefarious going on
> */
> - if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE,
> - "BUG: scratch buffer is too small"))
> - return -EINVAL;
> + if (WARN(work - scratch + pub_len + priv_len + creation_data_len +
> + creation_hash_len + creation_tk_len + (7 * 5) + 3 >
> + SCRATCH_SIZE,
> + "BUG: scratch buffer is too small")) {
> + rc = -EINVAL;
> + goto err;
> + }
>
> work = asn1_encode_integer(work, end_work, options->keyhandle);
> work = asn1_encode_octet_string(work, end_work, pub, pub_len);
> work = asn1_encode_octet_string(work, end_work, priv, priv_len);
> + if (creation_data_len) {
> + u8 *scratch2 = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
> + u8 *work2;
> + u8 *end_work2 = scratch2 + SCRATCH_SIZE;
> +
> + if (!scratch2) {
> + rc = -ENOMEM;
> + goto err;
> + }
> +
> + work2 = asn1_encode_octet_string(scratch2,
> + end_work2,
> + creation_data,
> + creation_data_len);
> +
> + work = asn1_encode_tag(work,
> + end_work,
> + 1,
> + scratch2,
> + work2 - scratch2);
> +
> + work2 = asn1_encode_octet_string(scratch2,
> + end_work2,
> + creation_hash,
> + creation_hash_len);
> +
> + work = asn1_encode_tag(work,
> + end_work,
> + 2,
> + scratch2,
> + work2 - scratch2);
> +
> + work2 = asn1_encode_octet_string(scratch2,
> + end_work2,
> + creation_tk,
> + creation_tk_len);
> +
> + work = asn1_encode_tag(work,
> + end_work,
> + 3,
> + scratch2,
> + work2 - scratch2);
> +
> + kfree(scratch2);
> + }
>
> work1 = payload->blob;
> work1 = asn1_encode_sequence(work1, work1 + sizeof(payload->blob),
> scratch, work - scratch);
> - if (WARN(IS_ERR(work1), "BUG: ASN.1 encoder failed"))
> - return PTR_ERR(work1);
> + if (WARN(IS_ERR(work1), "BUG: ASN.1 encoder failed")) {
> + rc = PTR_ERR(work1);
> + goto err;

I find the addition of the word "BUG" in a WARN() to be confusing. :) I
realize this is just copying the existing style, though.

> + }
>
> return work1 - payload->blob;
> +err:
> + kfree(scratch);
> + return rc;
> }
>
> struct tpm2_key_context {
> @@ -91,15 +169,21 @@ struct tpm2_key_context {
> u32 pub_len;
> const u8 *priv;
> u32 priv_len;
> + const u8 *creation_data;
> + u32 creation_data_len;
> + const u8 *creation_hash;
> + u32 creation_hash_len;
> + const u8 *creation_tk;
> + u32 creation_tk_len;
> };
>
> static int tpm2_key_decode(struct trusted_key_payload *payload,
> - struct trusted_key_options *options,
> - u8 **buf)
> + struct trusted_key_options *options)
> {
> + u64 data_len;
> int ret;
> struct tpm2_key_context ctx;
> - u8 *blob;
> + u8 *blob, *buf;
>
> memset(&ctx, 0, sizeof(ctx));
>
> @@ -108,21 +192,57 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
> if (ret < 0)
> return ret;
>
> - if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE)
> + data_len = ctx.priv_len + ctx.pub_len + ctx.creation_data_len +
> + ctx.creation_hash_len + ctx.creation_tk_len;
> +
> + if (data_len > MAX_BLOB_SIZE)
> return -EINVAL;
>
> - blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL);
> - if (!blob)
> + buf = kmalloc(data_len + 4, GFP_KERNEL);
> + if (!buf)
> return -ENOMEM;
>
> - *buf = blob;
> + blob = buf;
> options->keyhandle = ctx.parent;
>
> memcpy(blob, ctx.priv, ctx.priv_len);
> blob += ctx.priv_len;
>
> memcpy(blob, ctx.pub, ctx.pub_len);
> + blob += ctx.pub_len;
> + if (ctx.creation_data_len) {
> + memcpy(blob, ctx.creation_data, ctx.creation_data_len);
> + blob += ctx.creation_data_len;
> + }
> +
> + if (ctx.creation_hash_len) {
> + memcpy(blob, ctx.creation_hash, ctx.creation_hash_len);
> + blob += ctx.creation_hash_len;
> + }
>
> + if (ctx.creation_tk_len) {
> + memcpy(blob, ctx.creation_tk, ctx.creation_tk_len);
> + blob += ctx.creation_tk_len;
> + }
> +
> + /*
> + * Copy the buffer back into the payload blob since the creation
> + * info will be used after loading.
> + */
> + payload->blob_len = blob - buf;
> + memcpy(payload->blob, buf, payload->blob_len);
> + if (ctx.creation_data_len) {
> + payload->creation = payload->blob + ctx.priv_len + ctx.pub_len;
> + payload->creation_len = ctx.creation_data_len;
> + payload->creation_hash = payload->creation + ctx.creation_data_len;
> + payload->creation_hash_len = ctx.creation_hash_len;
> + payload->tk = payload->creation_hash +
> + payload->creation_hash_len;
> +
> + payload->tk_len = ctx.creation_tk_len;
> + }
> +
> + kfree(buf);
> return 0;
> }
>
> @@ -185,6 +305,42 @@ int tpm2_key_priv(void *context, size_t hdrlen,
> return 0;
> }
>
> +int tpm2_key_creation_data(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct tpm2_key_context *ctx = context;
> +
> + ctx->creation_data = value;
> + ctx->creation_data_len = vlen;
> +
> + return 0;
> +}

What is hdrlen here? Or rather, what kinds of bounds checking is needed
here?

> +
> +int tpm2_key_creation_hash(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct tpm2_key_context *ctx = context;
> +
> + ctx->creation_hash = value;
> + ctx->creation_hash_len = vlen;
> +
> + return 0;
> +}
> +
> +int tpm2_key_creation_tk(void *context, size_t hdrlen,
> + unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct tpm2_key_context *ctx = context;
> +
> + ctx->creation_tk = value;
> + ctx->creation_tk_len = vlen;
> +
> + return 0;
> +}
> +
> /**
> * tpm_buf_append_auth() - append TPMS_AUTH_COMMAND to the buffer.
> *
> @@ -229,6 +385,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> struct trusted_key_options *options)
> {
> int blob_len = 0;
> + unsigned int offset;
> struct tpm_buf buf;
> u32 hash;
> u32 flags;
> @@ -317,13 +474,14 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> rc = -E2BIG;
> goto out;
> }
> - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 4 + blob_len) {
> + offset = TPM_HEADER_SIZE + 4;
> + if (tpm_buf_length(&buf) < offset + blob_len) {
> rc = -EFAULT;
> goto out;
> }
>
> blob_len = tpm2_key_encode(payload, options,
> - &buf.data[TPM_HEADER_SIZE + 4],
> + &buf.data[offset],
> blob_len);
>
> out:
> @@ -370,13 +528,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> int rc;
> u32 attrs;
>
> - rc = tpm2_key_decode(payload, options, &blob);
> - if (rc) {
> - /* old form */
> - blob = payload->blob;
> + rc = tpm2_key_decode(payload, options);
> + if (rc)
> payload->old_format = 1;
> - }
>
> + blob = payload->blob;
> /* new format carries keyhandle but old format doesn't */
> if (!options->keyhandle)
> return -EINVAL;
> @@ -433,8 +589,6 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> (__be32 *) &buf.data[TPM_HEADER_SIZE]);
>
> out:
> - if (blob != payload->blob)
> - kfree(blob);
> tpm_buf_destroy(&buf);
>
> if (rc > 0)
> --
> 2.31.0
>

Otherwise looks good!

-Kees

--
Kees Cook