2022-08-23 22:28:27

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 00/10] Encrypted Hibernation

We are exploring enabling hibernation in some new scenarios. However,
our security team has a few requirements, listed below:
1. The hibernate image must be encrypted with protection derived from
both the platform (eg TPM) and user authentication data (eg
password).
2. Hibernation must not be a vector by which a malicious userspace can
escalate to the kernel.

Requirement #1 can be achieved solely with uswsusp, however requirement
2 necessitates mechanisms in the kernel to guarantee integrity of the
hibernate image. The kernel needs a way to authenticate that it generated
the hibernate image being loaded, and that the image has not been tampered
with. Adding support for in-kernel AEAD encryption with a TPM-sealed key
allows us to achieve both requirements with a single computation pass.

Matthew Garrett published a series [1] that aligns closely with this
goal. His series utilized the fact that PCR23 is a resettable PCR that
can be blocked from access by usermode. The TPM can create a sealed key
tied to PCR23 in two ways. First, the TPM can attest to the value of
PCR23 when the key was created, which the kernel can use on resume to
verify that the kernel must have created the key (since it is the only
one capable of modifying PCR23). It can also create a policy that enforces
PCR23 be set to a specific value as a condition of unsealing the key,
preventing usermode from unsealing the key by talking directly to the
TPM.

This series adopts that primitive as a foundation, tweaking and building
on it a bit. Where Matthew's series used the TPM-backed key to encrypt a
hash of the image, this series uses the key directly as a gcm(aes)
encryption key, which the kernel uses to encrypt and decrypt the
hibernate image in chunks of 16 pages. This provides both encryption and
integrity, which turns out to be a noticeable performance improvement over
separate passes for encryption and hashing.

The series also introduces the concept of mixing user key material into
the encryption key. This allows usermode to introduce key material
based on unspecified external authentication data (in our case derived
from something like the user password or PIN), without requiring
usermode to do a separate encryption pass.

Matthew also documented issues his series had [2] related to generating
fake images by booting alternate kernels without the PCR23 limiting.
With access to PCR23 on the same machine, usermode can create fake
hibernate images that are indistinguishable to the new kernel from
genuine ones. His post outlines a solution that involves adding more
PCRs into the creation data and policy, with some gyrations to make this
work well on a standard PC.

Our approach would be similar: on our machines PCR 0 indicates whether
the system is booted in secure/verified mode or developer mode. By
adding PCR0 to the policy, we can reject hibernate images made in
developer mode while in verified mode (or vice versa).

Additionally, mixing in the user authentication data limits both
data exfiltration attacks (eg a stolen laptop) and forged hibernation
image attacks to attackers that already know the authentication data (eg
user's password). This, combined with our relatively sealed userspace
(dm-verity on the rootfs), and some judicious clearing of the hibernate
image (such as across an OS update) further reduce the risk of an online
attack. The remaining attack space of a forgery from someone with
physical access to the device and knowledge of the authentication data
is out of scope for us, given that flipping to developer mode or
reflashing RO firmware trivially achieves the same thing.

A couple of patches still need to be written on top of this series. The
generalized functionality to OR in additional PCRs via Kconfig (like PCR
0 or 5) still needs to be added. We'll also need a patch that disallows
unencrypted forms of resume from hibernation, to fully close the door
to malicious userspace. However, I wanted to get this series out first
and get reactions from upstream before continuing to add to it.

[1] https://patchwork.kernel.org/project/linux-pm/cover/[email protected]/
[2] https://mjg59.dreamwidth.org/58077.html

Changes in v2:
- Fixed sparse warnings
- Adjust hash len by 2 due to new ASN.1 storage, and add underflow
check.
- Rework load/create_kernel_key() to eliminate a label (Andrey)
- Call put_device() needed from calling tpm_default_chip().
- Add missing static on snapshot_encrypted_byte_count()
- Fold in only the used kernel key bytes to the user key.
- Make the user key length 32 (Eric)
- Use CRYPTO_LIB_SHA256 for less boilerplate (Eric)
- Fixed some sparse warnings
- Use CRYPTO_LIB_SHA256 to get rid of sha256_data() (Eric)
- Adjusted offsets due to new ASN.1 format, and added a creation data
length check.
- Fix sparse warnings
- Fix session type comment (Andrey)
- Eliminate extra label in get/create_kernel_key() (Andrey)
- Call tpm_try_get_ops() before calling tpm2_flush_context().

Evan Green (7):
security: keys: trusted: Include TPM2 creation data
security: keys: trusted: Verify creation data
PM: hibernate: Add kernel-based encryption
PM: hibernate: Use TPM-backed keys to encrypt image
PM: hibernate: Mix user key in encrypted hibernate
PM: hibernate: Verify the digest encryption key
PM: hibernate: seal the encryption key with a PCR policy

Matthew Garrett (3):
tpm: Add support for in-kernel resetting of PCRs
tpm: Allow PCR 23 to be restricted to kernel-only use
security: keys: trusted: Allow storage of PCR values in creation data

Documentation/power/userland-swsusp.rst | 8 +
.../security/keys/trusted-encrypted.rst | 4 +
drivers/char/tpm/Kconfig | 10 +
drivers/char/tpm/tpm-dev-common.c | 8 +
drivers/char/tpm/tpm-interface.c | 28 +
drivers/char/tpm/tpm.h | 23 +
drivers/char/tpm/tpm1-cmd.c | 69 ++
drivers/char/tpm/tpm2-cmd.c | 58 +
drivers/char/tpm/tpm2-space.c | 2 +-
include/keys/trusted-type.h | 9 +
include/linux/tpm.h | 12 +
include/uapi/linux/suspend_ioctls.h | 28 +-
kernel/power/Kconfig | 16 +
kernel/power/Makefile | 1 +
kernel/power/power.h | 1 +
kernel/power/snapenc.c | 1037 +++++++++++++++++
kernel/power/snapshot.c | 5 +
kernel/power/user.c | 44 +-
kernel/power/user.h | 114 ++
security/keys/trusted-keys/tpm2key.asn1 | 5 +-
security/keys/trusted-keys/trusted_tpm1.c | 9 +
security/keys/trusted-keys/trusted_tpm2.c | 304 ++++-
22 files changed, 1754 insertions(+), 41 deletions(-)
create mode 100644 kernel/power/snapenc.c
create mode 100644 kernel/power/user.h

--
2.31.0


2022-08-23 22:28:54

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 04/10] security: keys: trusted: Allow storage of PCR values in creation data

From: Matthew Garrett <[email protected]>

When TPMs generate keys, they can also generate some information
describing the state of the PCRs at creation time. This data can then
later be certified by the TPM, allowing verification of the PCR values.
This allows us to determine the state of the system at the time a key
was generated. Add an additional argument to the trusted key creation
options, allowing the user to provide the set of PCRs that should have
their values incorporated into the creation data.

From: Matthew Garrett <[email protected]>
Signed-off-by: Matthew Garrett <[email protected]>

Signed-off-by: Evan Green <[email protected]>

---
Matthew's original version of this patch is at:
https://patchwork.kernel.org/patch/12096503/

(no changes since v1)

.../security/keys/trusted-encrypted.rst | 4 +++
include/keys/trusted-type.h | 1 +
security/keys/trusted-keys/trusted_tpm1.c | 9 +++++++
security/keys/trusted-keys/trusted_tpm2.c | 25 +++++++++++++++++--
4 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst
index 0bfb4c33974890..dc9e11bb4824da 100644
--- a/Documentation/security/keys/trusted-encrypted.rst
+++ b/Documentation/security/keys/trusted-encrypted.rst
@@ -199,6 +199,10 @@ Usage::
policyhandle= handle to an authorization policy session that defines the
same policy and with the same hash algorithm as was used to
seal the key.
+ creationpcrs= hex integer representing the set of PCR values to be
+ included in the PCR creation data. The bit corresponding
+ to each PCR should be 1 to be included, 0 to be ignored.
+ TPM2 only.

"keyctl print" returns an ascii hex copy of the sealed key, which is in standard
TPM_STORED_DATA format. The key length for new keys are always in bytes.
diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
index 209086fed240a5..8523d41507b2a4 100644
--- a/include/keys/trusted-type.h
+++ b/include/keys/trusted-type.h
@@ -54,6 +54,7 @@ struct trusted_key_options {
uint32_t policydigest_len;
unsigned char policydigest[MAX_DIGEST_SIZE];
uint32_t policyhandle;
+ uint32_t creation_pcrs;
};

struct trusted_key_ops {
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index aa108bea6739b3..2975827c01bec0 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -713,6 +713,7 @@ enum {
Opt_hash,
Opt_policydigest,
Opt_policyhandle,
+ Opt_creationpcrs,
};

static const match_table_t key_tokens = {
@@ -725,6 +726,7 @@ static const match_table_t key_tokens = {
{Opt_hash, "hash=%s"},
{Opt_policydigest, "policydigest=%s"},
{Opt_policyhandle, "policyhandle=%s"},
+ {Opt_creationpcrs, "creationpcrs=%s"},
{Opt_err, NULL}
};

@@ -858,6 +860,13 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
return -EINVAL;
opt->policyhandle = handle;
break;
+ case Opt_creationpcrs:
+ if (!tpm2)
+ return -EINVAL;
+ res = kstrtoint(args[0].from, 16, &opt->creation_pcrs);
+ if (res < 0)
+ return -EINVAL;
+ break;
default:
return -EINVAL;
}
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 1f166d4fa307a9..1d1470b880ca01 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -389,7 +389,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
struct tpm_buf buf;
u32 hash;
u32 flags;
- int i;
+ int i, j;
int rc;

for (i = 0; i < ARRAY_SIZE(tpm2_hash_map); i++) {
@@ -458,7 +458,28 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
tpm_buf_append_u16(&buf, 0);

/* creation PCR */
- tpm_buf_append_u32(&buf, 0);
+ if (options->creation_pcrs) {
+ /* One bank */
+ tpm_buf_append_u32(&buf, 1);
+ /* Which bank to use */
+ tpm_buf_append_u16(&buf, hash);
+ /* Length of the PCR bitmask */
+ tpm_buf_append_u8(&buf, 3);
+ /* PCR bitmask */
+ for (i = 0; i < 3; i++) {
+ char tmp = 0;
+
+ for (j = 0; j < 8; j++) {
+ char bit = (i * 8) + j;
+
+ if (options->creation_pcrs & (1 << bit))
+ tmp |= (1 << j);
+ }
+ tpm_buf_append_u8(&buf, tmp);
+ }
+ } else {
+ tpm_buf_append_u32(&buf, 0);
+ }

if (buf.flags & TPM_BUF_OVERFLOW) {
rc = -E2BIG;
--
2.31.0

2022-08-23 22:29:15

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 06/10] PM: hibernate: Add kernel-based encryption

Enabling the kernel to be able to do encryption and integrity checks on
the hibernate image prevents a malicious userspace from escalating to
kernel execution via hibernation resume. As a first step toward this, add
the scaffolding needed for the kernel to do AEAD encryption on the
hibernate image, giving us both secrecy and integrity.

We currently hardwire the encryption to be gcm(aes) in 16-page chunks.
This strikes a balance between minimizing the authentication tag
overhead on storage, and keeping a modest sized staging buffer. With
this chunk size, we'd generate 2MB of authentication tag data on an 8GB
hiberation image.

The encryption currently sits on top of the core snapshot functionality,
wired up only if requested in the uswsusp path. This could potentially
be lowered into the common snapshot code given a mechanism to stitch the
key contents into the image itself.

To avoid forcing usermode to deal with sequencing the auth tags in with
the data, we stitch the auth tags in to the snapshot after each chunk of
pages. This complicates the read and write functions, as we roll through
the flow of (for read) 1) fill the staging buffer with encrypted data,
2) feed the data pages out to user mode, 3) feed the tag out to user
mode. To avoid having each syscall return a small and variable amount
of data, the encrypted versions of read and write operate in a loop,
allowing an arbitrary amount of data through per syscall.

One alternative that would simplify things here would be a streaming
interface to AEAD. Then we could just stream the entire hibernate image
through directly, and handle a single tag at the end. However there is a
school of thought that suggests a streaming interface to AEAD represents
a loaded footgun, as it tempts the caller to act on the decrypted but
not yet verified data, defeating the purpose of AEAD.

With this change alone, we don't actually protect ourselves from
malicious userspace at all, since we kindly hand the key in plaintext
to usermode. In later changes, we'll seal the key with the TPM
before handing it back to usermode, so they can't decrypt or tamper with
the key themselves.

Signed-off-by: Evan Green <[email protected]>
---

(no changes since v1)

Documentation/power/userland-swsusp.rst | 8 +
include/uapi/linux/suspend_ioctls.h | 15 +-
kernel/power/Kconfig | 13 +
kernel/power/Makefile | 1 +
kernel/power/snapenc.c | 491 ++++++++++++++++++++++++
kernel/power/user.c | 40 +-
kernel/power/user.h | 101 +++++
7 files changed, 657 insertions(+), 12 deletions(-)
create mode 100644 kernel/power/snapenc.c
create mode 100644 kernel/power/user.h

diff --git a/Documentation/power/userland-swsusp.rst b/Documentation/power/userland-swsusp.rst
index 1cf62d80a9ca10..f759915a78ce98 100644
--- a/Documentation/power/userland-swsusp.rst
+++ b/Documentation/power/userland-swsusp.rst
@@ -115,6 +115,14 @@ SNAPSHOT_S2RAM
to resume the system from RAM if there's enough battery power or restore
its state on the basis of the saved suspend image otherwise)

+SNAPSHOT_ENABLE_ENCRYPTION
+ Enables encryption of the hibernate image within the kernel. Upon suspend
+ (ie when the snapshot device was opened for reading), returns a blob
+ representing the random encryption key the kernel created to encrypt the
+ hibernate image with. Upon resume (ie when the snapshot device was opened
+ for writing), receives a blob from usermode containing the key material
+ previously returned during hibernate.
+
The device's read() operation can be used to transfer the snapshot image from
the kernel. It has the following limitations:

diff --git a/include/uapi/linux/suspend_ioctls.h b/include/uapi/linux/suspend_ioctls.h
index bcce04e21c0dce..b73026ef824bb9 100644
--- a/include/uapi/linux/suspend_ioctls.h
+++ b/include/uapi/linux/suspend_ioctls.h
@@ -13,6 +13,18 @@ struct resume_swap_area {
__u32 dev;
} __attribute__((packed));

+#define USWSUSP_KEY_NONCE_SIZE 16
+
+/*
+ * This structure is used to pass the kernel's hibernate encryption key in
+ * either direction.
+ */
+struct uswsusp_key_blob {
+ __u32 blob_len;
+ __u8 blob[512];
+ __u8 nonce[USWSUSP_KEY_NONCE_SIZE];
+} __attribute__((packed));
+
#define SNAPSHOT_IOC_MAGIC '3'
#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1)
#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2)
@@ -29,6 +41,7 @@ struct resume_swap_area {
#define SNAPSHOT_PREF_IMAGE_SIZE _IO(SNAPSHOT_IOC_MAGIC, 18)
#define SNAPSHOT_AVAIL_SWAP_SIZE _IOR(SNAPSHOT_IOC_MAGIC, 19, __kernel_loff_t)
#define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)
-#define SNAPSHOT_IOC_MAXNR 20
+#define SNAPSHOT_ENABLE_ENCRYPTION _IOWR(SNAPSHOT_IOC_MAGIC, 21, struct uswsusp_key_blob)
+#define SNAPSHOT_IOC_MAXNR 21

#endif /* _LINUX_SUSPEND_IOCTLS_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 60a1d3051cc79a..cd574af0b43379 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -92,6 +92,19 @@ config HIBERNATION_SNAPSHOT_DEV

If in doubt, say Y.

+config ENCRYPTED_HIBERNATION
+ bool "Encryption support for userspace snapshots"
+ depends on HIBERNATION_SNAPSHOT_DEV
+ depends on CRYPTO_AEAD2=y
+ default n
+ help
+ Enable support for kernel-based encryption of hibernation snapshots
+ created by uswsusp tools.
+
+ Say N if userspace handles the image encryption.
+
+ If in doubt, say N.
+
config PM_STD_PARTITION
string "Default resume partition"
depends on HIBERNATION
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 874ad834dc8daf..7be08f2e0e3b68 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_SUSPEND) += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o
obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) += user.o
+obj-$(CONFIG_ENCRYPTED_HIBERNATION) += snapenc.o
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o
obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o

diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
new file mode 100644
index 00000000000000..cb90692d6ab83a
--- /dev/null
+++ b/kernel/power/snapenc.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* This file provides encryption support for system snapshots. */
+
+#include <linux/crypto.h>
+#include <crypto/aead.h>
+#include <crypto/gcm.h>
+#include <linux/random.h>
+#include <linux/mm.h>
+#include <linux/uaccess.h>
+
+#include "power.h"
+#include "user.h"
+
+/* Encrypt more data from the snapshot into the staging area. */
+static int snapshot_encrypt_refill(struct snapshot_data *data)
+{
+
+ u8 nonce[GCM_AES_IV_SIZE];
+ int pg_idx;
+ int res;
+ struct aead_request *req = data->aead_req;
+ DECLARE_CRYPTO_WAIT(wait);
+ size_t total = 0;
+
+ /*
+ * The first buffer is the associated data, set to the offset to prevent
+ * attacks that rearrange chunks.
+ */
+ sg_set_buf(&data->sg[0], &data->crypt_total, sizeof(data->crypt_total));
+
+ /* Load the crypt buffer with snapshot pages. */
+ for (pg_idx = 0; pg_idx < CHUNK_SIZE; pg_idx++) {
+ void *buf = data->crypt_pages[pg_idx];
+
+ res = snapshot_read_next(&data->handle);
+ if (res < 0)
+ return res;
+ if (res == 0)
+ break;
+
+ WARN_ON(res != PAGE_SIZE);
+
+ /*
+ * Copy the page into the staging area. A future optimization
+ * could potentially skip this copy for lowmem pages.
+ */
+ memcpy(buf, data_of(data->handle), PAGE_SIZE);
+ sg_set_buf(&data->sg[1 + pg_idx], buf, PAGE_SIZE);
+ total += PAGE_SIZE;
+ }
+
+ sg_set_buf(&data->sg[1 + pg_idx], &data->auth_tag, SNAPSHOT_AUTH_TAG_SIZE);
+ aead_request_set_callback(req, 0, crypto_req_done, &wait);
+ /*
+ * Use incrementing nonces for each chunk, since a 64 bit value won't
+ * roll into re-use for any given hibernate image.
+ */
+ memcpy(&nonce[0], &data->nonce_low, sizeof(data->nonce_low));
+ memcpy(&nonce[sizeof(data->nonce_low)],
+ &data->nonce_high,
+ sizeof(nonce) - sizeof(data->nonce_low));
+
+ data->nonce_low += 1;
+ /* Total does not include AAD or the auth tag. */
+ aead_request_set_crypt(req, data->sg, data->sg, total, nonce);
+ res = crypto_wait_req(crypto_aead_encrypt(req), &wait);
+ if (res)
+ return res;
+
+ data->crypt_size = total;
+ data->crypt_total += total;
+ return 0;
+}
+
+/* Decrypt data from the staging area and push it to the snapshot. */
+static int snapshot_decrypt_drain(struct snapshot_data *data)
+{
+ u8 nonce[GCM_AES_IV_SIZE];
+ int page_count;
+ int pg_idx;
+ int res;
+ struct aead_request *req = data->aead_req;
+ DECLARE_CRYPTO_WAIT(wait);
+ size_t total;
+
+ /* Set up the associated data. */
+ sg_set_buf(&data->sg[0], &data->crypt_total, sizeof(data->crypt_total));
+
+ /*
+ * Get the number of full pages, which could be short at the end. There
+ * should also be a tag at the end, so the offset won't be an even page.
+ */
+ page_count = data->crypt_offset >> PAGE_SHIFT;
+ total = page_count << PAGE_SHIFT;
+ if ((total == 0) || (total == data->crypt_offset))
+ return -EINVAL;
+
+ /*
+ * Load the sg list with the crypt buffer. Inline decrypt back into the
+ * staging buffer. A future optimization could decrypt directly into
+ * lowmem pages.
+ */
+ for (pg_idx = 0; pg_idx < page_count; pg_idx++)
+ sg_set_buf(&data->sg[1 + pg_idx], data->crypt_pages[pg_idx], PAGE_SIZE);
+
+ /*
+ * It's possible this is the final decrypt, and there are fewer than
+ * CHUNK_SIZE pages. If this is the case we would have just written the
+ * auth tag into the first few bytes of a new page. Copy to the tag if
+ * so.
+ */
+ if ((page_count < CHUNK_SIZE) &&
+ (data->crypt_offset - total) == sizeof(data->auth_tag)) {
+
+ memcpy(data->auth_tag,
+ data->crypt_pages[pg_idx],
+ sizeof(data->auth_tag));
+
+ } else if (data->crypt_offset !=
+ ((CHUNK_SIZE << PAGE_SHIFT) + SNAPSHOT_AUTH_TAG_SIZE)) {
+
+ return -EINVAL;
+ }
+
+ sg_set_buf(&data->sg[1 + pg_idx], &data->auth_tag, SNAPSHOT_AUTH_TAG_SIZE);
+ aead_request_set_callback(req, 0, crypto_req_done, &wait);
+ memcpy(&nonce[0], &data->nonce_low, sizeof(data->nonce_low));
+ memcpy(&nonce[sizeof(data->nonce_low)],
+ &data->nonce_high,
+ sizeof(nonce) - sizeof(data->nonce_low));
+
+ data->nonce_low += 1;
+ aead_request_set_crypt(req, data->sg, data->sg, total + SNAPSHOT_AUTH_TAG_SIZE, nonce);
+ res = crypto_wait_req(crypto_aead_decrypt(req), &wait);
+ if (res)
+ return res;
+
+ data->crypt_size = 0;
+ data->crypt_offset = 0;
+
+ /* Push the decrypted pages further down the stack. */
+ total = 0;
+ for (pg_idx = 0; pg_idx < page_count; pg_idx++) {
+ void *buf = data->crypt_pages[pg_idx];
+
+ res = snapshot_write_next(&data->handle);
+ if (res < 0)
+ return res;
+ if (res == 0)
+ break;
+
+ if (!data_of(data->handle))
+ return -EINVAL;
+
+ WARN_ON(res != PAGE_SIZE);
+
+ /*
+ * Copy the page into the staging area. A future optimization
+ * could potentially skip this copy for lowmem pages.
+ */
+ memcpy(data_of(data->handle), buf, PAGE_SIZE);
+ total += PAGE_SIZE;
+ }
+
+ data->crypt_total += total;
+ return 0;
+}
+
+static ssize_t snapshot_read_next_encrypted(struct snapshot_data *data,
+ void **buf)
+{
+ size_t tag_off;
+
+ /* Refill the encrypted buffer if it's empty. */
+ if ((data->crypt_size == 0) ||
+ (data->crypt_offset >=
+ (data->crypt_size + SNAPSHOT_AUTH_TAG_SIZE))) {
+
+ int rc;
+
+ data->crypt_size = 0;
+ data->crypt_offset = 0;
+ rc = snapshot_encrypt_refill(data);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* Return data pages if the offset is in that region. */
+ if (data->crypt_offset < data->crypt_size) {
+ size_t pg_idx = data->crypt_offset >> PAGE_SHIFT;
+ size_t pg_off = data->crypt_offset & (PAGE_SIZE - 1);
+ *buf = data->crypt_pages[pg_idx] + pg_off;
+ return PAGE_SIZE - pg_off;
+ }
+
+ /* Use offsets just beyond the size to return the tag. */
+ tag_off = data->crypt_offset - data->crypt_size;
+ if (tag_off > SNAPSHOT_AUTH_TAG_SIZE)
+ tag_off = SNAPSHOT_AUTH_TAG_SIZE;
+
+ *buf = data->auth_tag + tag_off;
+ return SNAPSHOT_AUTH_TAG_SIZE - tag_off;
+}
+
+static ssize_t snapshot_write_next_encrypted(struct snapshot_data *data,
+ void **buf)
+{
+ size_t tag_off;
+
+ /* Return data pages if the offset is in that region. */
+ if (data->crypt_offset < (PAGE_SIZE * CHUNK_SIZE)) {
+ size_t pg_idx = data->crypt_offset >> PAGE_SHIFT;
+ size_t pg_off = data->crypt_offset & (PAGE_SIZE - 1);
+ *buf = data->crypt_pages[pg_idx] + pg_off;
+ return PAGE_SIZE - pg_off;
+ }
+
+ /* Use offsets just beyond the size to return the tag. */
+ tag_off = data->crypt_offset - (PAGE_SIZE * CHUNK_SIZE);
+ if (tag_off > SNAPSHOT_AUTH_TAG_SIZE)
+ tag_off = SNAPSHOT_AUTH_TAG_SIZE;
+
+ *buf = data->auth_tag + tag_off;
+ return SNAPSHOT_AUTH_TAG_SIZE - tag_off;
+}
+
+ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count, loff_t *offp)
+{
+ ssize_t total = 0;
+
+ /* Loop getting buffers of varying sizes and copying to userspace. */
+ while (count) {
+ size_t copy_size;
+ size_t not_done;
+ void *src;
+ ssize_t src_size = snapshot_read_next_encrypted(data, &src);
+
+ if (src_size <= 0) {
+ if (total == 0)
+ return src_size;
+
+ break;
+ }
+
+ copy_size = min(count, (size_t)src_size);
+ not_done = copy_to_user(buf + total, src, copy_size);
+ copy_size -= not_done;
+ total += copy_size;
+ count -= copy_size;
+ data->crypt_offset += copy_size;
+ if (copy_size == 0) {
+ if (total == 0)
+ return -EFAULT;
+
+ break;
+ }
+ }
+
+ *offp += total;
+ return total;
+}
+
+ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf, size_t count, loff_t *offp)
+{
+ ssize_t total = 0;
+
+ /* Loop getting buffers of varying sizes and copying from. */
+ while (count) {
+ size_t copy_size;
+ size_t not_done;
+ void *dst;
+ ssize_t dst_size = snapshot_write_next_encrypted(data, &dst);
+
+ if (dst_size <= 0) {
+ if (total == 0)
+ return dst_size;
+
+ break;
+ }
+
+ copy_size = min(count, (size_t)dst_size);
+ not_done = copy_from_user(dst, buf + total, copy_size);
+ copy_size -= not_done;
+ total += copy_size;
+ count -= copy_size;
+ data->crypt_offset += copy_size;
+ if (copy_size == 0) {
+ if (total == 0)
+ return -EFAULT;
+
+ break;
+ }
+
+ /* Drain the encrypted buffer if it's full. */
+ if ((data->crypt_offset >=
+ ((PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE))) {
+
+ int rc;
+
+ rc = snapshot_decrypt_drain(data);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ *offp += total;
+ return total;
+}
+
+void snapshot_teardown_encryption(struct snapshot_data *data)
+{
+ int i;
+
+ if (data->aead_req) {
+ aead_request_free(data->aead_req);
+ data->aead_req = NULL;
+ }
+
+ if (data->aead_tfm) {
+ crypto_free_aead(data->aead_tfm);
+ data->aead_tfm = NULL;
+ }
+
+ for (i = 0; i < CHUNK_SIZE; i++) {
+ if (data->crypt_pages[i]) {
+ free_page((unsigned long)data->crypt_pages[i]);
+ data->crypt_pages[i] = NULL;
+ }
+ }
+}
+
+static int snapshot_setup_encryption_common(struct snapshot_data *data)
+{
+ int i, rc;
+
+ data->crypt_total = 0;
+ data->crypt_offset = 0;
+ data->crypt_size = 0;
+ memset(data->crypt_pages, 0, sizeof(data->crypt_pages));
+ /* This only works once per hibernate. */
+ if (data->aead_tfm)
+ return -EINVAL;
+
+ /* Set up the encryption transform */
+ data->aead_tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+ if (IS_ERR(data->aead_tfm)) {
+ rc = PTR_ERR(data->aead_tfm);
+ data->aead_tfm = NULL;
+ return rc;
+ }
+
+ rc = -ENOMEM;
+ data->aead_req = aead_request_alloc(data->aead_tfm, GFP_KERNEL);
+ if (data->aead_req == NULL)
+ goto setup_fail;
+
+ /* Allocate the staging area */
+ for (i = 0; i < CHUNK_SIZE; i++) {
+ data->crypt_pages[i] = (void *)__get_free_page(GFP_ATOMIC);
+ if (data->crypt_pages[i] == NULL)
+ goto setup_fail;
+ }
+
+ sg_init_table(data->sg, CHUNK_SIZE + 2);
+
+ /*
+ * The associated data will be the offset so that blocks can't be
+ * rearranged.
+ */
+ aead_request_set_ad(data->aead_req, sizeof(data->crypt_total));
+ rc = crypto_aead_setauthsize(data->aead_tfm, SNAPSHOT_AUTH_TAG_SIZE);
+ if (rc)
+ goto setup_fail;
+
+ return 0;
+
+setup_fail:
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ u8 aead_key[SNAPSHOT_ENCRYPTION_KEY_SIZE];
+ u8 nonce[USWSUSP_KEY_NONCE_SIZE];
+ int rc;
+ /* Don't pull a random key from a world that can be reset. */
+ if (data->ready)
+ return -EPIPE;
+
+ rc = snapshot_setup_encryption_common(data);
+ if (rc)
+ return rc;
+
+ /* Build a random starting nonce. */
+ get_random_bytes(nonce, sizeof(nonce));
+ memcpy(&data->nonce_low, &nonce[0], sizeof(data->nonce_low));
+ memcpy(&data->nonce_high, &nonce[8], sizeof(data->nonce_high));
+ /* Build a random key */
+ get_random_bytes(aead_key, sizeof(aead_key));
+ rc = crypto_aead_setkey(data->aead_tfm, aead_key, sizeof(aead_key));
+ if (rc)
+ goto fail;
+
+ /* Hand the key back to user mode (to be changed!) */
+ rc = put_user(sizeof(struct uswsusp_key_blob), &key->blob_len);
+ if (rc)
+ goto fail;
+
+ rc = copy_to_user(&key->blob, &aead_key, sizeof(aead_key));
+ if (rc)
+ goto fail;
+
+ rc = copy_to_user(&key->nonce, &nonce, sizeof(nonce));
+ if (rc)
+ goto fail;
+
+ return 0;
+
+fail:
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+int snapshot_set_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ struct uswsusp_key_blob blob;
+ int rc;
+
+ /* It's too late if data's been pushed in. */
+ if (data->handle.cur)
+ return -EPIPE;
+
+ rc = snapshot_setup_encryption_common(data);
+ if (rc)
+ return rc;
+
+ /* Load the key from user mode. */
+ rc = copy_from_user(&blob, key, sizeof(struct uswsusp_key_blob));
+ if (rc)
+ goto crypto_setup_fail;
+
+ if (blob.blob_len != sizeof(struct uswsusp_key_blob)) {
+ rc = -EINVAL;
+ goto crypto_setup_fail;
+ }
+
+ rc = crypto_aead_setkey(data->aead_tfm,
+ blob.blob,
+ SNAPSHOT_ENCRYPTION_KEY_SIZE);
+
+ if (rc)
+ goto crypto_setup_fail;
+
+ /* Load the starting nonce. */
+ memcpy(&data->nonce_low, &blob.nonce[0], sizeof(data->nonce_low));
+ memcpy(&data->nonce_high, &blob.nonce[8], sizeof(data->nonce_high));
+ return 0;
+
+crypto_setup_fail:
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+{
+ loff_t pages = raw_size >> PAGE_SHIFT;
+ loff_t chunks = (pages + (CHUNK_SIZE - 1)) / CHUNK_SIZE;
+ /*
+ * The encrypted size is the normal size, plus a stitched in
+ * authentication tag for every chunk of pages.
+ */
+ return raw_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+}
+
+int snapshot_finalize_decrypted_image(struct snapshot_data *data)
+{
+ int rc;
+
+ if (data->crypt_offset != 0) {
+ rc = snapshot_decrypt_drain(data);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/kernel/power/user.c b/kernel/power/user.c
index d43c2aa583b26b..348e55f623b7e1 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -25,19 +25,10 @@
#include <linux/uaccess.h>

#include "power.h"
+#include "user.h"

static bool need_wait;
-
-static struct snapshot_data {
- struct snapshot_handle handle;
- int swap;
- int mode;
- bool frozen;
- bool ready;
- bool platform_support;
- bool free_bitmaps;
- dev_t dev;
-} snapshot_state;
+struct snapshot_data snapshot_state;

int is_hibernate_resume_dev(dev_t dev)
{
@@ -120,6 +111,7 @@ static int snapshot_release(struct inode *inode, struct file *filp)
} else if (data->free_bitmaps) {
free_basic_memory_bitmaps();
}
+ snapshot_teardown_encryption(data);
pm_notifier_call_chain(data->mode == O_RDONLY ?
PM_POST_HIBERNATION : PM_POST_RESTORE);
hibernate_release();
@@ -143,6 +135,12 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf,
res = -ENODATA;
goto Unlock;
}
+
+ if (snapshot_encryption_enabled(data)) {
+ res = snapshot_read_encrypted(data, buf, count, offp);
+ goto Unlock;
+ }
+
if (!pg_offp) { /* on page boundary? */
res = snapshot_read_next(&data->handle);
if (res <= 0)
@@ -178,6 +176,11 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf,

data = filp->private_data;

+ if (snapshot_encryption_enabled(data)) {
+ res = snapshot_write_encrypted(data, buf, count, offp);
+ goto unlock;
+ }
+
if (!pg_offp) {
res = snapshot_write_next(&data->handle);
if (res <= 0)
@@ -313,6 +316,12 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
break;

case SNAPSHOT_ATOMIC_RESTORE:
+ if (snapshot_encryption_enabled(data)) {
+ error = snapshot_finalize_decrypted_image(data);
+ if (error)
+ break;
+ }
+
snapshot_write_finalize(&data->handle);
if (data->mode != O_WRONLY || !data->frozen ||
!snapshot_image_loaded(&data->handle)) {
@@ -348,6 +357,8 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
}
size = snapshot_get_image_size();
size <<= PAGE_SHIFT;
+ if (snapshot_encryption_enabled(data))
+ size = snapshot_get_encrypted_image_size(size);
error = put_user(size, (loff_t __user *)arg);
break;

@@ -405,6 +416,13 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = snapshot_set_swap_area(data, (void __user *)arg);
break;

+ case SNAPSHOT_ENABLE_ENCRYPTION:
+ if (data->mode == O_RDONLY)
+ error = snapshot_get_encryption_key(data, (void __user *)arg);
+ else
+ error = snapshot_set_encryption_key(data, (void __user *)arg);
+ break;
+
default:
error = -ENOTTY;

diff --git a/kernel/power/user.h b/kernel/power/user.h
new file mode 100644
index 00000000000000..6823e2eba7ec53
--- /dev/null
+++ b/kernel/power/user.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/crypto.h>
+#include <crypto/aead.h>
+#include <crypto/aes.h>
+
+#define SNAPSHOT_ENCRYPTION_KEY_SIZE AES_KEYSIZE_128
+#define SNAPSHOT_AUTH_TAG_SIZE 16
+
+/* Define the number of pages in a single AEAD encryption chunk. */
+#define CHUNK_SIZE 16
+
+struct snapshot_data {
+ struct snapshot_handle handle;
+ int swap;
+ int mode;
+ bool frozen;
+ bool ready;
+ bool platform_support;
+ bool free_bitmaps;
+ dev_t dev;
+
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ struct crypto_aead *aead_tfm;
+ struct aead_request *aead_req;
+ void *crypt_pages[CHUNK_SIZE];
+ u8 auth_tag[SNAPSHOT_AUTH_TAG_SIZE];
+ struct scatterlist sg[CHUNK_SIZE + 2]; /* Add room for AD and auth tag. */
+ size_t crypt_offset;
+ size_t crypt_size;
+ uint64_t crypt_total;
+ uint64_t nonce_low;
+ uint64_t nonce_high;
+#endif
+
+};
+
+extern struct snapshot_data snapshot_state;
+
+/* kernel/power/swapenc.c routines */
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+
+ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count, loff_t *offp);
+
+ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf, size_t count, loff_t *offp);
+
+void snapshot_teardown_encryption(struct snapshot_data *data);
+int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key);
+
+int snapshot_set_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key);
+
+loff_t snapshot_get_encrypted_image_size(loff_t raw_size);
+
+int snapshot_finalize_decrypted_image(struct snapshot_data *data);
+
+#define snapshot_encryption_enabled(data) (!!(data)->aead_tfm)
+
+#else
+
+ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count, loff_t *offp)
+{
+ return -ENOTTY;
+}
+
+ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf, size_t count, loff_t *offp)
+{
+ return -ENOTTY;
+}
+
+static void snapshot_teardown_encryption(struct snapshot_data *data) {}
+static int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ return -ENOTTY;
+}
+
+static int snapshot_set_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ return -ENOTTY;
+}
+
+static loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+{
+ return raw_size;
+}
+
+static int snapshot_finalize_decrypted_image(struct snapshot_data *data)
+{
+ return -ENOTTY;
+}
+
+#define snapshot_encryption_enabled(data) (0)
+
+#endif
--
2.31.0

2022-08-23 22:29:23

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 07/10] PM: hibernate: Use TPM-backed keys to encrypt image

When using encrypted hibernate images, have the TPM create a key for us
and seal it. By handing back a sealed blob instead of the raw key, we
prevent usermode from being able to decrypt and tamper with the
hibernate image on a different machine.

We'll also go through the motions of having PCR23 set to a known value at
the time of key creation and unsealing. Currently there's nothing that
enforces the contents of PCR23 as a condition to unseal the key blob,
that will come in a later change.

Sourced-from: Matthew Garrett <[email protected]>
Signed-off-by: Evan Green <[email protected]>

---
Matthew's incarnation of this patch is at:
https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/

Changes in v2:
- Rework load/create_kernel_key() to eliminate a label (Andrey)
- Call put_device() needed from calling tpm_default_chip().

kernel/power/Kconfig | 2 +
kernel/power/snapenc.c | 207 +++++++++++++++++++++++++++++++++++++++--
kernel/power/user.h | 1 +
3 files changed, 201 insertions(+), 9 deletions(-)

diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index cd574af0b43379..ff15361c5d8fad 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -96,6 +96,8 @@ config ENCRYPTED_HIBERNATION
bool "Encryption support for userspace snapshots"
depends on HIBERNATION_SNAPSHOT_DEV
depends on CRYPTO_AEAD2=y
+ depends on KEYS
+ depends on TRUSTED_KEYS
default n
help
Enable support for kernel-based encryption of hibernation snapshots
diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
index cb90692d6ab83a..90079f6d4f184b 100644
--- a/kernel/power/snapenc.c
+++ b/kernel/power/snapenc.c
@@ -4,13 +4,23 @@
#include <linux/crypto.h>
#include <crypto/aead.h>
#include <crypto/gcm.h>
+#include <keys/trusted-type.h>
+#include <linux/key-type.h>
#include <linux/random.h>
#include <linux/mm.h>
+#include <linux/tpm.h>
#include <linux/uaccess.h>

#include "power.h"
#include "user.h"

+/* sha256("To sleep, perchance to dream") */
+static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
+ .digest = {0x92, 0x78, 0x3d, 0x79, 0x2d, 0x00, 0x31, 0xb0, 0x55, 0xf9,
+ 0x1e, 0x0d, 0xce, 0x83, 0xde, 0x1d, 0xc4, 0xc5, 0x8e, 0x8c,
+ 0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
+ 0x5f, 0x49}};
+
/* Encrypt more data from the snapshot into the staging area. */
static int snapshot_encrypt_refill(struct snapshot_data *data)
{
@@ -313,6 +323,12 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
{
int i;

+ if (data->key) {
+ key_revoke(data->key);
+ key_put(data->key);
+ data->key = NULL;
+ }
+
if (data->aead_req) {
aead_request_free(data->aead_req);
data->aead_req = NULL;
@@ -381,11 +397,83 @@ static int snapshot_setup_encryption_common(struct snapshot_data *data)
return rc;
}

+static int snapshot_create_kernel_key(struct snapshot_data *data)
+{
+ const struct cred *cred = current_cred();
+ struct tpm_digest *digests = NULL;
+ struct tpm_chip *chip;
+ struct key *key = NULL;
+ int ret, i;
+ /* Create a key sealed by the SRK. */
+ char *keyinfo = "new\t32\tkeyhandle=0x81000000";
+
+ chip = tpm_default_chip();
+ if (!chip)
+ return -ENODEV;
+
+ if (!(tpm_is_tpm2(chip))) {
+ ret = -ENODEV;
+ goto out_dev;
+ }
+
+ ret = tpm_pcr_reset(chip, 23);
+ if (ret)
+ goto out;
+
+ digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
+ GFP_KERNEL);
+ if (!digests) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i <= chip->nr_allocated_banks; i++) {
+ digests[i].alg_id = chip->allocated_banks[i].alg_id;
+ if (digests[i].alg_id == known_digest.alg_id)
+ memcpy(&digests[i], &known_digest, sizeof(known_digest));
+ }
+
+ ret = tpm_pcr_extend(chip, 23, digests);
+ if (ret != 0)
+ goto out;
+
+ key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
+ GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
+ NULL);
+
+ if (IS_ERR(key)) {
+ ret = PTR_ERR(key);
+ key = NULL;
+ goto out;
+ }
+
+ ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
+ NULL);
+ if (ret != 0)
+ goto out;
+
+ data->key = key;
+ key = NULL;
+
+out:
+ if (key) {
+ key_revoke(key);
+ key_put(key);
+ }
+
+ kfree(digests);
+ tpm_pcr_reset(chip, 23);
+
+out_dev:
+ put_device(&chip->dev);
+ return ret;
+}
+
int snapshot_get_encryption_key(struct snapshot_data *data,
struct uswsusp_key_blob __user *key)
{
- u8 aead_key[SNAPSHOT_ENCRYPTION_KEY_SIZE];
u8 nonce[USWSUSP_KEY_NONCE_SIZE];
+ struct trusted_key_payload *payload;
int rc;
/* Don't pull a random key from a world that can be reset. */
if (data->ready)
@@ -399,21 +487,28 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
get_random_bytes(nonce, sizeof(nonce));
memcpy(&data->nonce_low, &nonce[0], sizeof(data->nonce_low));
memcpy(&data->nonce_high, &nonce[8], sizeof(data->nonce_high));
- /* Build a random key */
- get_random_bytes(aead_key, sizeof(aead_key));
- rc = crypto_aead_setkey(data->aead_tfm, aead_key, sizeof(aead_key));
+
+ /* Create a kernel key, and set it. */
+ rc = snapshot_create_kernel_key(data);
+ if (rc)
+ goto fail;
+
+ payload = data->key->payload.data[0];
+ /* Install the key */
+ rc = crypto_aead_setkey(data->aead_tfm, payload->key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
if (rc)
goto fail;

- /* Hand the key back to user mode (to be changed!) */
- rc = put_user(sizeof(struct uswsusp_key_blob), &key->blob_len);
+ /* Hand the key back to user mode in sealed form. */
+ rc = put_user(payload->blob_len, &key->blob_len);
if (rc)
goto fail;

- rc = copy_to_user(&key->blob, &aead_key, sizeof(aead_key));
+ rc = copy_to_user(&key->blob, &payload->blob, payload->blob_len);
if (rc)
goto fail;

+ /* The nonce just gets handed back in the clear. */
rc = copy_to_user(&key->nonce, &nonce, sizeof(nonce));
if (rc)
goto fail;
@@ -425,10 +520,99 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
return rc;
}

+static int snapshot_load_kernel_key(struct snapshot_data *data,
+ struct uswsusp_key_blob *blob)
+{
+
+ const struct cred *cred = current_cred();
+ char *keytemplate = "load\t%s\tkeyhandle=0x81000000";
+ struct tpm_digest *digests = NULL;
+ char *blobstring = NULL;
+ char *keyinfo = NULL;
+ struct tpm_chip *chip;
+ struct key *key = NULL;
+ int i, ret;
+
+ chip = tpm_default_chip();
+ if (!chip)
+ return -ENODEV;
+
+ if (!(tpm_is_tpm2(chip))) {
+ ret = -ENODEV;
+ goto out_dev;
+ }
+
+ ret = tpm_pcr_reset(chip, 23);
+ if (ret)
+ goto out;
+
+ digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
+ GFP_KERNEL);
+ if (!digests)
+ goto out;
+
+ for (i = 0; i <= chip->nr_allocated_banks; i++) {
+ digests[i].alg_id = chip->allocated_banks[i].alg_id;
+ if (digests[i].alg_id == known_digest.alg_id)
+ memcpy(&digests[i], &known_digest, sizeof(known_digest));
+ }
+
+ ret = tpm_pcr_extend(chip, 23, digests);
+ if (ret != 0)
+ goto out;
+
+ blobstring = kmalloc(blob->blob_len * 2, GFP_KERNEL);
+ if (!blobstring) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ bin2hex(blobstring, blob->blob, blob->blob_len);
+ keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring);
+ if (!keyinfo) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
+ GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
+ NULL);
+
+ if (IS_ERR(key)) {
+ ret = PTR_ERR(key);
+ key = NULL;
+ goto out;
+ }
+
+ ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
+ NULL);
+ if (ret != 0)
+ goto out;
+
+ data->key = key;
+ key = NULL;
+
+out:
+ if (key) {
+ key_revoke(key);
+ key_put(key);
+ }
+
+ kfree(keyinfo);
+ kfree(blobstring);
+ kfree(digests);
+ tpm_pcr_reset(chip, 23);
+
+out_dev:
+ put_device(&chip->dev);
+ return ret;
+}
+
int snapshot_set_encryption_key(struct snapshot_data *data,
struct uswsusp_key_blob __user *key)
{
struct uswsusp_key_blob blob;
+ struct trusted_key_payload *payload;
int rc;

/* It's too late if data's been pushed in. */
@@ -444,13 +628,18 @@ int snapshot_set_encryption_key(struct snapshot_data *data,
if (rc)
goto crypto_setup_fail;

- if (blob.blob_len != sizeof(struct uswsusp_key_blob)) {
+ if (blob.blob_len > sizeof(key->blob)) {
rc = -EINVAL;
goto crypto_setup_fail;
}

+ rc = snapshot_load_kernel_key(data, &blob);
+ if (rc)
+ goto crypto_setup_fail;
+
+ payload = data->key->payload.data[0];
rc = crypto_aead_setkey(data->aead_tfm,
- blob.blob,
+ payload->key,
SNAPSHOT_ENCRYPTION_KEY_SIZE);

if (rc)
diff --git a/kernel/power/user.h b/kernel/power/user.h
index 6823e2eba7ec53..591b30bb213349 100644
--- a/kernel/power/user.h
+++ b/kernel/power/user.h
@@ -31,6 +31,7 @@ struct snapshot_data {
uint64_t crypt_total;
uint64_t nonce_low;
uint64_t nonce_high;
+ struct key *key;
#endif

};
--
2.31.0

2022-08-23 22:29:40

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 01/10] tpm: Add support for in-kernel resetting of PCRs

From: Matthew Garrett <[email protected]>

Add an internal command for resetting a PCR. This will be used by the
encrypted hibernation code to set PCR23 to a known value. The
hibernation code will seal the hibernation key with a policy specifying
PCR23 be set to this known value as a mechanism to ensure that the
hibernation key is genuine. But to do this repeatedly, resetting the PCR
is necessary as well.

From: Matthew Garrett <[email protected]>
Signed-off-by: Matthew Garrett <[email protected]>

Signed-off-by: Evan Green <[email protected]>
---
Matthew's original version of this patch was at:
https://patchwork.kernel.org/patch/12096487/

(no changes since v1)

drivers/char/tpm/tpm-interface.c | 28 +++++++++++++++++++++++++
drivers/char/tpm/tpm.h | 2 ++
drivers/char/tpm/tpm1-cmd.c | 34 ++++++++++++++++++++++++++++++
drivers/char/tpm/tpm2-cmd.c | 36 ++++++++++++++++++++++++++++++++
include/linux/tpm.h | 7 +++++++
5 files changed, 107 insertions(+)

diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index 1621ce8187052c..17b8643ee109c2 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -342,6 +342,34 @@ int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
}
EXPORT_SYMBOL_GPL(tpm_pcr_extend);

+/**
+ * tpm_pcr_reset - reset the specified PCR
+ * @chip: a &struct tpm_chip instance, %NULL for the default chip
+ * @pcr_idx: the PCR to be reset
+ *
+ * Return: same as with tpm_transmit_cmd()
+ */
+int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx)
+{
+ int rc;
+
+ chip = tpm_find_get_ops(chip);
+ if (!chip)
+ return -ENODEV;
+
+ if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+ rc = tpm2_pcr_reset(chip, pcr_idx);
+ goto out;
+ }
+
+ rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
+
+out:
+ tpm_put_ops(chip);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(tpm_pcr_reset);
+
/**
* tpm_send - send a TPM command
* @chip: a &struct tpm_chip instance, %NULL for the default chip
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 24ee4e1cc452a0..a80b341d38eb8c 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -175,6 +175,7 @@ int tpm1_get_timeouts(struct tpm_chip *chip);
unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
const char *log_msg);
+int tpm1_pcr_reset(struct tpm_chip *chip, u32 pcr_idx, const char *log_msg);
int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf);
ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
const char *desc, size_t min_cap_length);
@@ -217,6 +218,7 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest, u16 *digest_size_ptr);
int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
+int tpm2_pcr_reset(struct tpm_chip *chip, u32 pcr_idx);
int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max);
ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,
u32 *value, const char *desc);
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index cf64c738510529..8ec743dec26544 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -478,6 +478,40 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
return rc;
}

+struct tpm_pcr_selection {
+ u16 size_of_select;
+ u8 pcr_select[3];
+} __packed;
+
+#define TPM_ORD_PCR_RESET 200
+int tpm1_pcr_reset(struct tpm_chip *chip, u32 pcr_idx, const char *log_msg)
+{
+ struct tpm_pcr_selection selection;
+ struct tpm_buf buf;
+ int i, rc;
+ char tmp;
+
+ rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_RESET);
+ if (rc)
+ return rc;
+
+ selection.size_of_select = 3;
+
+ for (i = 0; i < selection.size_of_select; i++) {
+ tmp = 0;
+ if (pcr_idx / 3 == i) {
+ pcr_idx -= i * 8;
+ tmp |= 1 << pcr_idx;
+ }
+ selection.pcr_select[i] = tmp;
+ }
+ tpm_buf_append(&buf, (u8 *)&selection, sizeof(selection));
+
+ rc = tpm_transmit_cmd(chip, &buf, sizeof(selection), log_msg);
+ tpm_buf_destroy(&buf);
+ return rc;
+}
+
#define TPM_ORD_GET_CAP 101
ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
const char *desc, size_t min_cap_length)
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 65d03867e114c5..69126a6770386e 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -269,6 +269,42 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
return rc;
}

+/**
+ * tpm2_pcr_reset() - reset a PCR
+ *
+ * @chip: TPM chip to use.
+ * @pcr_idx: index of the PCR.
+ *
+ * Return: Same as with tpm_transmit_cmd.
+ */
+int tpm2_pcr_reset(struct tpm_chip *chip, u32 pcr_idx)
+{
+ struct tpm_buf buf;
+ struct tpm2_null_auth_area auth_area;
+ int rc;
+
+ rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_RESET);
+ if (rc)
+ return rc;
+
+ tpm_buf_append_u32(&buf, pcr_idx);
+
+ auth_area.handle = cpu_to_be32(TPM2_RS_PW);
+ auth_area.nonce_size = 0;
+ auth_area.attributes = 0;
+ auth_area.auth_size = 0;
+
+ tpm_buf_append_u32(&buf, sizeof(struct tpm2_null_auth_area));
+ tpm_buf_append(&buf, (const unsigned char *)&auth_area,
+ sizeof(auth_area));
+
+ rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to reset a PCR");
+
+ tpm_buf_destroy(&buf);
+
+ return rc;
+}
+
struct tpm2_get_random_out {
__be16 size;
u8 buffer[TPM_MAX_RNG_DATA];
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index dfeb25a0362dee..8320cbac6f4009 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -219,6 +219,7 @@ enum tpm2_command_codes {
TPM2_CC_HIERARCHY_CONTROL = 0x0121,
TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129,
TPM2_CC_CREATE_PRIMARY = 0x0131,
+ TPM2_CC_PCR_RESET = 0x013D,
TPM2_CC_SEQUENCE_COMPLETE = 0x013E,
TPM2_CC_SELF_TEST = 0x0143,
TPM2_CC_STARTUP = 0x0144,
@@ -423,6 +424,7 @@ extern ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_buf *buf,
size_t min_rsp_body_length, const char *desc);
extern int tpm_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest);
+extern int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx);
extern int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
extern int tpm_send(struct tpm_chip *chip, void *cmd, size_t buflen);
@@ -440,6 +442,11 @@ static inline int tpm_pcr_read(struct tpm_chip *chip, int pcr_idx,
return -ENODEV;
}

+static inline int tpm_pcr_reset(struct tpm_chip *chip, int pcr_idx)
+{
+ return -ENODEV;
+}
+
static inline int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests)
{
--
2.31.0

2022-08-23 22:29:52

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 10/10] PM: hibernate: seal the encryption key with a PCR policy

The key blob is not secret, and by default the TPM will happily unseal
it regardless of system state. We can protect against that by sealing
the secret with a PCR policy - if the current PCR state doesn't match,
the TPM will refuse to release the secret. For now let's just seal it to
PCR 23. In the long term we may want a more flexible policy around this,
such as including PCR 7 for PCs or 0 for Chrome OS.

Sourced-from: Matthew Garrett <[email protected]>
Signed-off-by: Evan Green <[email protected]>
---
The original version of this patch is here:
https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/

Changes in v2:
- Fix sparse warnings
- Fix session type comment (Andrey)
- Eliminate extra label in get/create_kernel_key() (Andrey)
- Call tpm_try_get_ops() before calling tpm2_flush_context().

include/linux/tpm.h | 4 +
kernel/power/snapenc.c | 164 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 164 insertions(+), 4 deletions(-)

diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 438f8bc0a50582..cd520efc515bca 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -233,18 +233,22 @@ enum tpm2_command_codes {
TPM2_CC_CONTEXT_LOAD = 0x0161,
TPM2_CC_CONTEXT_SAVE = 0x0162,
TPM2_CC_FLUSH_CONTEXT = 0x0165,
+ TPM2_CC_START_AUTH_SESSION = 0x0176,
TPM2_CC_VERIFY_SIGNATURE = 0x0177,
TPM2_CC_GET_CAPABILITY = 0x017A,
TPM2_CC_GET_RANDOM = 0x017B,
TPM2_CC_PCR_READ = 0x017E,
+ TPM2_CC_POLICY_PCR = 0x017F,
TPM2_CC_PCR_EXTEND = 0x0182,
TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185,
TPM2_CC_HASH_SEQUENCE_START = 0x0186,
+ TPM2_CC_POLICY_GET_DIGEST = 0x0189,
TPM2_CC_CREATE_LOADED = 0x0191,
TPM2_CC_LAST = 0x0193, /* Spec 1.36 */
};

enum tpm2_permanent_handles {
+ TPM2_RH_NULL = 0x40000007,
TPM2_RS_PW = 0x40000009,
};

diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
index 1f08942450775a..02d25f9500cb7f 100644
--- a/kernel/power/snapenc.c
+++ b/kernel/power/snapenc.c
@@ -433,6 +433,111 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
memset(data->user_key, 0, sizeof(data->user_key));
}

+static int tpm_setup_policy(struct tpm_chip *chip, int *session_handle)
+{
+ struct tpm_header *head;
+ struct tpm_buf buf;
+ char nonce[32] = {0x00};
+ int rc;
+
+ rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS,
+ TPM2_CC_START_AUTH_SESSION);
+ if (rc)
+ return rc;
+
+ /* Decrypt key */
+ tpm_buf_append_u32(&buf, TPM2_RH_NULL);
+
+ /* Auth entity */
+ tpm_buf_append_u32(&buf, TPM2_RH_NULL);
+
+ /* Nonce - blank is fine here */
+ tpm_buf_append_u16(&buf, sizeof(nonce));
+ tpm_buf_append(&buf, nonce, sizeof(nonce));
+
+ /* Encrypted secret - empty */
+ tpm_buf_append_u16(&buf, 0);
+
+ /* Session type - policy */
+ tpm_buf_append_u8(&buf, 0x01);
+
+ /* Encryption type - NULL */
+ tpm_buf_append_u16(&buf, TPM_ALG_NULL);
+
+ /* Hash type - SHA256 */
+ tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+
+ rc = tpm_send(chip, buf.data, tpm_buf_length(&buf));
+ if (rc)
+ goto out;
+
+ head = (struct tpm_header *)buf.data;
+ if (be32_to_cpu(head->length) != sizeof(struct tpm_header) +
+ sizeof(u32) + sizeof(u16) + sizeof(nonce)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ *session_handle = be32_to_cpu(*(__be32 *)&buf.data[10]);
+ memcpy(nonce, &buf.data[16], sizeof(nonce));
+ tpm_buf_destroy(&buf);
+ rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_POLICY_PCR);
+ if (rc)
+ return rc;
+
+ tpm_buf_append_u32(&buf, *session_handle);
+
+ /* PCR digest - read from the PCR, we'll verify creation data later */
+ tpm_buf_append_u16(&buf, 0);
+
+ /* One PCR */
+ tpm_buf_append_u32(&buf, 1);
+
+ /* SHA256 banks */
+ tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+
+ /* Select PCR 23 */
+ tpm_buf_append_u32(&buf, 0x03000080);
+ rc = tpm_send(chip, buf.data, tpm_buf_length(&buf));
+ if (rc)
+ goto out;
+
+out:
+ tpm_buf_destroy(&buf);
+ return rc;
+}
+
+static int tpm_policy_get_digest(struct tpm_chip *chip, int handle,
+ char *digest)
+{
+ struct tpm_header *head;
+ struct tpm_buf buf;
+ int rc;
+
+ rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_POLICY_GET_DIGEST);
+ if (rc)
+ return rc;
+
+ tpm_buf_append_u32(&buf, handle);
+ rc = tpm_send(chip, buf.data, tpm_buf_length(&buf));
+
+ if (rc)
+ goto out;
+
+ head = (struct tpm_header *)buf.data;
+ if (be32_to_cpu(head->length) != sizeof(struct tpm_header) +
+ sizeof(u16) + SHA256_DIGEST_SIZE) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ memcpy(digest, &buf.data[12], SHA256_DIGEST_SIZE);
+
+out:
+ tpm_buf_destroy(&buf);
+ return rc;
+}
+
static int snapshot_setup_encryption_common(struct snapshot_data *data)
{
int i, rc;
@@ -492,7 +597,12 @@ static int snapshot_create_kernel_key(struct snapshot_data *data)
struct key *key = NULL;
int ret, i;
/* Create a key sealed by the SRK. */
- char *keyinfo = "new\t32\tkeyhandle=0x81000000\tcreationpcrs=0x00800000";
+ char *keyinfo = NULL;
+ const char *keytemplate =
+ "new\t32\tkeyhandle=0x81000000\tcreationpcrs=0x00800000\tpolicydigest=%s";
+ char policy[SHA256_DIGEST_SIZE];
+ char *policydigest = NULL;
+ int session_handle = -1;

chip = tpm_default_chip();
if (!chip)
@@ -524,6 +634,28 @@ static int snapshot_create_kernel_key(struct snapshot_data *data)
if (ret != 0)
goto out;

+ policydigest = kmalloc(SHA256_DIGEST_SIZE * 2 + 1, GFP_KERNEL);
+ if (!policydigest) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = tpm_setup_policy(chip, &session_handle);
+ if (ret != 0)
+ goto out;
+
+ ret = tpm_policy_get_digest(chip, session_handle, policy);
+ if (ret != 0)
+ goto out;
+
+ bin2hex(policydigest, policy, SHA256_DIGEST_SIZE);
+ policydigest[SHA256_DIGEST_SIZE * 2] = '\0';
+ keyinfo = kasprintf(GFP_KERNEL, keytemplate, policydigest);
+ if (!keyinfo) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
NULL);
@@ -548,7 +680,16 @@ static int snapshot_create_kernel_key(struct snapshot_data *data)
key_put(key);
}

+ if (session_handle != -1) {
+ if (tpm_try_get_ops(chip) == 0) {
+ tpm2_flush_context(chip, session_handle);
+ tpm_put_ops(chip);
+ }
+ }
+
kfree(digests);
+ kfree(keyinfo);
+ kfree(policydigest);
tpm_pcr_reset(chip, 23);

out_dev:
@@ -613,13 +754,14 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,

char certhash[SHA256_DIGEST_SIZE];
const struct cred *cred = current_cred();
- char *keytemplate = "load\t%s\tkeyhandle=0x81000000";
+ char *keytemplate = "load\t%s\tkeyhandle=0x81000000\tpolicyhandle=0x%x";
struct tpm_digest *digests = NULL;
char *blobstring = NULL;
char *keyinfo = NULL;
struct tpm_chip *chip;
struct key *key = NULL;
struct trusted_key_payload *payload;
+ int session_handle = -1;
int i, ret;

chip = tpm_default_chip();
@@ -652,14 +794,21 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,
if (ret != 0)
goto out;

- blobstring = kmalloc(blob->blob_len * 2, GFP_KERNEL);
+ ret = tpm_setup_policy(chip, &session_handle);
+ if (ret != 0)
+ goto out;
+
+ blobstring = kmalloc(blob->blob_len * 2 + 1, GFP_KERNEL);
if (!blobstring) {
ret = -ENOMEM;
goto out;
}

bin2hex(blobstring, blob->blob, blob->blob_len);
- keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring);
+ blobstring[blob->blob_len * 2] = '\0';
+ keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring,
+ session_handle);
+
if (!keyinfo) {
ret = -ENOMEM;
goto out;
@@ -742,6 +891,13 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,
key_put(key);
}

+ if (session_handle != -1) {
+ if (tpm_try_get_ops(chip) == 0) {
+ tpm2_flush_context(chip, session_handle);
+ tpm_put_ops(chip);
+ }
+ }
+
kfree(keyinfo);
kfree(blobstring);
kfree(digests);
--
2.31.0

2022-08-23 22:33:55

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 09/10] PM: hibernate: Verify the digest encryption key

We want to ensure that the key used to encrypt the digest was created by
the kernel during hibernation. To do this we request that the TPM
include information about the value of PCR 23 at the time of key
creation in the sealed blob. On resume, we can make sure that the PCR
information in the creation data blob (already certified by the TPM to
be accurate) corresponds to the expected value. Since only
the kernel can touch PCR 23, if an attacker generates a key themselves
the value of PCR 23 will have been different, allowing us to reject the
key and boot normally instead of resuming.

Sourced-from: Matthew Garrett <[email protected]>
Signed-off-by: Evan Green <[email protected]>

---
Matthew's original version of this patch is here:
https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/

I moved the TPM2_CC_CERTIFYCREATION code into a separate change in the
trusted key code because the blob_handle was being flushed and was no
longer valid for use in CC_CERTIFYCREATION after the key was loaded. As
an added benefit of moving the certification into the trusted keys code,
we can drop the other patch from the original series that squirrelled
the blob_handle away.

Changes in v2:
- Fixed some sparse warnings
- Use CRYPTO_LIB_SHA256 to get rid of sha256_data() (Eric)
- Adjusted offsets due to new ASN.1 format, and added a creation data
length check.

kernel/power/snapenc.c | 67 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 65 insertions(+), 2 deletions(-)

diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
index e0b902d2dcf13a..1f08942450775a 100644
--- a/kernel/power/snapenc.c
+++ b/kernel/power/snapenc.c
@@ -22,6 +22,12 @@ static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
0x5f, 0x49}};

+/* sha256(sha256(empty_pcr | known_digest)) */
+static const char expected_digest[] = {0x2f, 0x96, 0xf2, 0x1b, 0x70, 0xa9, 0xe8,
+ 0x42, 0x25, 0x8e, 0x66, 0x07, 0xbe, 0xbc, 0xe3, 0x1f, 0x2c, 0x84, 0x4a,
+ 0x3f, 0x85, 0x17, 0x31, 0x47, 0x9a, 0xa5, 0x53, 0xbb, 0x23, 0x0c, 0x32,
+ 0xf3};
+
/* Derive a key from the kernel and user keys for data encryption. */
static int snapshot_use_user_key(struct snapshot_data *data)
{
@@ -486,7 +492,7 @@ static int snapshot_create_kernel_key(struct snapshot_data *data)
struct key *key = NULL;
int ret, i;
/* Create a key sealed by the SRK. */
- char *keyinfo = "new\t32\tkeyhandle=0x81000000";
+ char *keyinfo = "new\t32\tkeyhandle=0x81000000\tcreationpcrs=0x00800000";

chip = tpm_default_chip();
if (!chip)
@@ -605,6 +611,7 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,
struct uswsusp_key_blob *blob)
{

+ char certhash[SHA256_DIGEST_SIZE];
const struct cred *cred = current_cred();
char *keytemplate = "load\t%s\tkeyhandle=0x81000000";
struct tpm_digest *digests = NULL;
@@ -612,6 +619,7 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,
char *keyinfo = NULL;
struct tpm_chip *chip;
struct key *key = NULL;
+ struct trusted_key_payload *payload;
int i, ret;

chip = tpm_default_chip();
@@ -629,8 +637,10 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,

digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
GFP_KERNEL);
- if (!digests)
+ if (!digests) {
+ ret = -ENOMEM;
goto out;
+ }

for (i = 0; i <= chip->nr_allocated_banks; i++) {
digests[i].alg_id = chip->allocated_banks[i].alg_id;
@@ -670,6 +680,59 @@ static int snapshot_load_kernel_key(struct snapshot_data *data,
if (ret != 0)
goto out;

+ /* Verify the creation hash matches the creation data. */
+ payload = key->payload.data[0];
+ if (!payload->creation || !payload->creation_hash ||
+ (payload->creation_len < 3) ||
+ (payload->creation_hash_len < SHA256_DIGEST_SIZE)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ sha256(payload->creation + 2, payload->creation_len - 2, certhash);
+ if (memcmp(payload->creation_hash + 2, certhash, SHA256_DIGEST_SIZE) != 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* We now know that the creation data is authentic - parse it */
+
+ /* TPML_PCR_SELECTION.count */
+ if (be32_to_cpu(*(__be32 *)&payload->creation[2]) != 1) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (be16_to_cpu(*(__be16 *)&payload->creation[6]) != TPM_ALG_SHA256) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (*(char *)&payload->creation[8] != 3) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* PCR 23 selected */
+ if (be32_to_cpu(*(__be32 *)&payload->creation[8]) != 0x03000080) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (be16_to_cpu(*(__be16 *)&payload->creation[12]) !=
+ SHA256_DIGEST_SIZE) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Verify PCR 23 contained the expected value when the key was created. */
+ if (memcmp(&payload->creation[14], expected_digest,
+ SHA256_DIGEST_SIZE) != 0) {
+
+ ret = -EINVAL;
+ goto out;
+ }
+
data->key = key;
key = NULL;

--
2.31.0

2022-08-23 22:34:55

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 03/10] security: keys: trusted: Include TPM2 creation data

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.

Signed-off-by: Evan Green <[email protected]>
---
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})
}
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 2b2c8eb258d5bd..1f166d4fa307a9 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -37,7 +37,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
u8 *work = scratch, *work1;
u8 *end_work = scratch + SCRATCH_SIZE;
u8 *priv, *pub;
+ u8 *creation_data = NULL, *creation_hash = NULL, *creation_tk = NULL;
+ u16 creation_data_len, creation_hash_len = 0, creation_tk_len = 0;
u16 priv_len, pub_len;
+ int rc;

priv_len = get_unaligned_be16(src) + 2;
priv = src;
@@ -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;
@@ -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;
+ }

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;
+}
+
+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

2022-08-23 22:48:16

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

From: Matthew Garrett <[email protected]>

Under certain circumstances it might be desirable to enable the creation
of TPM-backed secrets that are only accessible to the kernel. In an
ideal world this could be achieved by using TPM localities, but these
don't appear to be available on consumer systems. An alternative is to
simply block userland from modifying one of the resettable PCRs, leaving
it available to the kernel. If the kernel ensures that no userland can
access the TPM while it is carrying out work, it can reset PCR 23,
extend it to an arbitrary value, create or load a secret, and then reset
the PCR again. Even if userland somehow obtains the sealed material, it
will be unable to unseal it since PCR 23 will never be in the
appropriate state.

From: Matthew Garrett <[email protected]>
Signed-off-by: Matthew Garrett <[email protected]>

Signed-off-by: Evan Green <[email protected]>
---
Matthew's original version of this patch is at:
https://patchwork.kernel.org/patch/12096491/

Changes in v2:
- Fixed sparse warnings

drivers/char/tpm/Kconfig | 10 +++++++++
drivers/char/tpm/tpm-dev-common.c | 8 +++++++
drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
drivers/char/tpm/tpm2-space.c | 2 +-
6 files changed, 97 insertions(+), 1 deletion(-)

diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 927088b2c3d3f2..4483b61a428b11 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -211,4 +211,14 @@ config TCG_FTPM_TEE
This driver proxies for firmware TPM running in TEE.

source "drivers/char/tpm/st33zp24/Kconfig"
+
+config TCG_TPM_RESTRICT_PCR
+ bool "Restrict userland access to PCR 23"
+ depends on TCG_TPM
+ help
+ If set, block userland from extending or resetting PCR 23. This
+ allows it to be restricted to in-kernel use, preventing userland
+ from being able to make use of data sealed to the TPM by the kernel.
+ This is required for secure hibernation support, but should be left
+ disabled if any userland may require access to PCR23.
endif # TCG_TPM
diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
index dc4c0a0a512903..7a4e618c7d1942 100644
--- a/drivers/char/tpm/tpm-dev-common.c
+++ b/drivers/char/tpm/tpm-dev-common.c
@@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
priv->response_read = false;
*off = 0;

+ if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
+ ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
+ else
+ ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
+
+ if (ret)
+ goto out;
+
/*
* If in nonblocking mode schedule an async job to send
* the command return the size.
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index a80b341d38eb8c..077c3ca0a127ba 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
int tpm2_probe(struct tpm_chip *chip);
int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
+int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
+ const void *buf, size_t bufsiz);
int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
@@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
void tpm_bios_log_teardown(struct tpm_chip *chip);
int tpm_dev_common_init(void);
void tpm_dev_common_exit(void);
+
+#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
+#define TPM_RESTRICTED_PCR 23
+
+int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
+int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
+#else
+static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
+ size_t size)
+{
+ return 0;
+}
+
+static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
+ size_t size)
+{
+ return 0;
+}
+#endif
#endif
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index 8ec743dec26544..318e75ae42fb85 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)

return 0;
}
+
+#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
+int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
+{
+ struct tpm_header *header = (struct tpm_header *)buffer;
+ char len, offset;
+ __be32 *pcr;
+ int pos;
+
+ switch (be32_to_cpu(header->ordinal)) {
+ case TPM_ORD_PCR_EXTEND:
+ if (size < (TPM_HEADER_SIZE + sizeof(u32)))
+ return -EINVAL;
+ pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
+ if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
+ return -EPERM;
+ break;
+ case TPM_ORD_PCR_RESET:
+ if (size < (TPM_HEADER_SIZE + 1))
+ return -EINVAL;
+ len = buffer[TPM_HEADER_SIZE];
+ if (size < (TPM_HEADER_SIZE + 1 + len))
+ return -EINVAL;
+ offset = TPM_RESTRICTED_PCR/3;
+ if (len < offset)
+ break;
+ pos = TPM_HEADER_SIZE + 1 + offset;
+ if (buffer[pos] & (1 << (TPM_RESTRICTED_PCR - 2 * offset)))
+ return -EPERM;
+ break;
+ }
+
+ return 0;
+}
+#endif
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 69126a6770386e..dbf7f5552c6782 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -821,3 +821,25 @@ int tpm2_find_cc(struct tpm_chip *chip, u32 cc)

return -1;
}
+
+#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
+int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
+{
+ int cc = tpm_find_and_validate_cc(chip, NULL, buffer, size);
+ __be32 *handle;
+
+ switch (cc) {
+ case TPM2_CC_PCR_EXTEND:
+ case TPM2_CC_PCR_RESET:
+ if (size < (TPM_HEADER_SIZE + sizeof(u32)))
+ return -EINVAL;
+
+ handle = (__be32 *)&buffer[TPM_HEADER_SIZE];
+ if (be32_to_cpu(*handle) == TPM_RESTRICTED_PCR)
+ return -EPERM;
+ break;
+ }
+
+ return 0;
+}
+#endif
diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
index ffb35f0154c16c..6f51cd92c6400f 100644
--- a/drivers/char/tpm/tpm2-space.c
+++ b/drivers/char/tpm/tpm2-space.c
@@ -262,7 +262,7 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd)
return 0;
}

-static int tpm_find_and_validate_cc(struct tpm_chip *chip,
+int tpm_find_and_validate_cc(struct tpm_chip *chip,
struct tpm_space *space,
const void *cmd, size_t len)
{
--
2.31.0

2022-08-23 22:49:42

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 05/10] security: keys: trusted: Verify creation data

If a loaded key contains creation data, ask the TPM to verify that
creation data. This allows users like encrypted hibernate to know that
the loaded and parsed creation data has not been tampered with.

Partially-sourced-from: Matthew Garrett <[email protected]>
Signed-off-by: Evan Green <[email protected]>

---
Source material for this change is at:
https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/

Changes in v2:
- Adjust hash len by 2 due to new ASN.1 storage, and add underflow
check.

include/linux/tpm.h | 1 +
security/keys/trusted-keys/trusted_tpm2.c | 77 ++++++++++++++++++++++-
2 files changed, 77 insertions(+), 1 deletion(-)

diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 8320cbac6f4009..438f8bc0a50582 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -224,6 +224,7 @@ enum tpm2_command_codes {
TPM2_CC_SELF_TEST = 0x0143,
TPM2_CC_STARTUP = 0x0144,
TPM2_CC_SHUTDOWN = 0x0145,
+ TPM2_CC_CERTIFYCREATION = 0x014A,
TPM2_CC_NV_READ = 0x014E,
TPM2_CC_CREATE = 0x0153,
TPM2_CC_LOAD = 0x0157,
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 1d1470b880ca01..f81c6578c7f783 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -691,6 +691,74 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
return rc;
}

+/**
+ * tpm2_certify_creation() - execute a TPM2_CertifyCreation command
+ *
+ * @chip: TPM chip to use
+ * @payload: the key data in clear and encrypted form
+ * @blob_handle: the loaded TPM handle of the key
+ *
+ * Return: 0 on success
+ * -EINVAL on tpm error status
+ * < 0 error from tpm_send or tpm_buf_init
+ */
+static int tpm2_certify_creation(struct tpm_chip *chip,
+ struct trusted_key_payload *payload,
+ u32 blob_handle)
+{
+ struct tpm_header *head;
+ struct tpm_buf buf;
+ int rc;
+
+ rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CERTIFYCREATION);
+ if (rc)
+ return rc;
+
+ /* Use TPM_RH_NULL for signHandle */
+ tpm_buf_append_u32(&buf, 0x40000007);
+
+ /* Object handle */
+ tpm_buf_append_u32(&buf, blob_handle);
+
+ /* Auth */
+ tpm_buf_append_u32(&buf, 9);
+ tpm_buf_append_u32(&buf, TPM2_RS_PW);
+ tpm_buf_append_u16(&buf, 0);
+ tpm_buf_append_u8(&buf, 0);
+ tpm_buf_append_u16(&buf, 0);
+
+ /* Qualifying data */
+ tpm_buf_append_u16(&buf, 0);
+
+ /* Creation data hash */
+ if (payload->creation_hash_len < 2) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ tpm_buf_append_u16(&buf, payload->creation_hash_len - 2);
+ tpm_buf_append(&buf, payload->creation_hash + 2,
+ payload->creation_hash_len - 2);
+
+ /* signature scheme */
+ tpm_buf_append_u16(&buf, TPM_ALG_NULL);
+
+ /* creation ticket */
+ tpm_buf_append(&buf, payload->tk, payload->tk_len);
+
+ rc = tpm_transmit_cmd(chip, &buf, 6, "certifying creation data");
+ if (rc)
+ goto out;
+
+ head = (struct tpm_header *)buf.data;
+
+ if (head->return_code != 0)
+ rc = -EINVAL;
+out:
+ tpm_buf_destroy(&buf);
+ return rc;
+}
+
/**
* tpm2_unseal_trusted() - unseal the payload of a trusted key
*
@@ -716,8 +784,15 @@ int tpm2_unseal_trusted(struct tpm_chip *chip,
goto out;

rc = tpm2_unseal_cmd(chip, payload, options, blob_handle);
- tpm2_flush_context(chip, blob_handle);
+ if (rc)
+ goto flush;
+
+ if (payload->creation_len)
+ rc = tpm2_certify_creation(chip, payload, blob_handle);

+
+flush:
+ tpm2_flush_context(chip, blob_handle);
out:
tpm_put_ops(chip);

--
2.31.0

2022-08-23 23:23:17

by Evan Green

[permalink] [raw]
Subject: [PATCH v2 08/10] PM: hibernate: Mix user key in encrypted hibernate

Usermode may have their own data protection requirements when it comes
to encrypting the hibernate image. For example, users may want a policy
where the hibernate image is protected by a key derived both from
platform-level security as well as authentication data (such as a
password or PIN). This way, even if the platform is compromised (ie a
stolen laptop), sensitive data cannot be exfiltrated via the hibernate
image without additional data (like the user's password).

The kernel is already doing the encryption, but will be protecting its
key with the TPM alone. Allow usermode to mix in key content of their own
for the data portion of the hibernate image, so that the image
encryption key is determined both by a TPM-backed secret and
user-defined data.

To mix the user key in, we hash the kernel key followed by the user key,
and use the resulting hash as the new key. This allows usermode to mix
in its key material without giving it too much control over what key is
actually driving the encryption (which might be used to attack the
secret kernel key).

Limiting this to the data portion allows the kernel to receive the page
map and prepare its giant allocation even if this user key is not yet
available (ie the user has not yet finished typing in their password).
Once the user key becomes available, the data portion can be pushed
through to the kernel as well. This enables "preloading" scenarios,
where the hibernate image is loaded off of disk while the additional
key material (eg password) is being collected.

One annoyance of the "preloading" scheme is that hibernate image memory
is effectively double-allocated: first by the usermode process pulling
encrypted contents off of disk and holding it, and second by the kernel
in its giant allocation in prepare_image(). An interesting future
optimization would be to allow the kernel to accept and store encrypted
page data before the user key is available. This would remove the
double allocation problem, as usermode could push the encrypted pages
loaded from disk immediately without storing them. The kernel could defer
decryption of the data until the user key is available, while still
knowing the correct page locations to store the encrypted data in.

Signed-off-by: Evan Green <[email protected]>
---

Changes in v2:
- Add missing static on snapshot_encrypted_byte_count()
- Fold in only the used kernel key bytes to the user key.
- Make the user key length 32 (Eric)
- Use CRYPTO_LIB_SHA256 for less boilerplate (Eric)

include/uapi/linux/suspend_ioctls.h | 15 ++-
kernel/power/Kconfig | 1 +
kernel/power/power.h | 1 +
kernel/power/snapenc.c | 158 ++++++++++++++++++++++++++--
kernel/power/snapshot.c | 5 +
kernel/power/user.c | 4 +
kernel/power/user.h | 12 +++
7 files changed, 185 insertions(+), 11 deletions(-)

diff --git a/include/uapi/linux/suspend_ioctls.h b/include/uapi/linux/suspend_ioctls.h
index b73026ef824bb9..f93a22eac52dc2 100644
--- a/include/uapi/linux/suspend_ioctls.h
+++ b/include/uapi/linux/suspend_ioctls.h
@@ -25,6 +25,18 @@ struct uswsusp_key_blob {
__u8 nonce[USWSUSP_KEY_NONCE_SIZE];
} __attribute__((packed));

+/*
+ * Allow user mode to fold in key material for the data portion of the hibernate
+ * image.
+ */
+struct uswsusp_user_key {
+ /* Kernel returns the metadata size. */
+ __kernel_loff_t meta_size;
+ __u32 key_len;
+ __u8 key[32];
+ __u32 pad;
+};
+
#define SNAPSHOT_IOC_MAGIC '3'
#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1)
#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2)
@@ -42,6 +54,7 @@ struct uswsusp_key_blob {
#define SNAPSHOT_AVAIL_SWAP_SIZE _IOR(SNAPSHOT_IOC_MAGIC, 19, __kernel_loff_t)
#define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)
#define SNAPSHOT_ENABLE_ENCRYPTION _IOWR(SNAPSHOT_IOC_MAGIC, 21, struct uswsusp_key_blob)
-#define SNAPSHOT_IOC_MAXNR 21
+#define SNAPSHOT_SET_USER_KEY _IOWR(SNAPSHOT_IOC_MAGIC, 22, struct uswsusp_user_key)
+#define SNAPSHOT_IOC_MAXNR 22

#endif /* _LINUX_SUSPEND_IOCTLS_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index ff15361c5d8fad..6ff14d92454711 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -98,6 +98,7 @@ config ENCRYPTED_HIBERNATION
depends on CRYPTO_AEAD2=y
depends on KEYS
depends on TRUSTED_KEYS
+ select CRYPTO_LIB_SHA256
default n
help
Enable support for kernel-based encryption of hibernation snapshots
diff --git a/kernel/power/power.h b/kernel/power/power.h
index b4f43394320961..5955e5cf692302 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -151,6 +151,7 @@ struct snapshot_handle {

extern unsigned int snapshot_additional_pages(struct zone *zone);
extern unsigned long snapshot_get_image_size(void);
+extern unsigned long snapshot_get_meta_page_count(void);
extern int snapshot_read_next(struct snapshot_handle *handle);
extern int snapshot_write_next(struct snapshot_handle *handle);
extern void snapshot_write_finalize(struct snapshot_handle *handle);
diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
index 90079f6d4f184b..e0b902d2dcf13a 100644
--- a/kernel/power/snapenc.c
+++ b/kernel/power/snapenc.c
@@ -6,6 +6,7 @@
#include <crypto/gcm.h>
#include <keys/trusted-type.h>
#include <linux/key-type.h>
+#include <crypto/sha.h>
#include <linux/random.h>
#include <linux/mm.h>
#include <linux/tpm.h>
@@ -21,6 +22,38 @@ static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
0x5f, 0x49}};

+/* Derive a key from the kernel and user keys for data encryption. */
+static int snapshot_use_user_key(struct snapshot_data *data)
+{
+ u8 digest[SHA256_DIGEST_SIZE];
+ struct trusted_key_payload *payload = data->key->payload.data[0];
+ struct sha256_state sha256_state;
+
+ /*
+ * Hash the kernel key and the user key together. This folds in the user
+ * key, but not in a way that gives the user mode predictable control
+ * over the key bits.
+ */
+ sha256_init(&sha256_state);
+ sha256_update(&sha256_state, payload->key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ sha256_update(&sha256_state, data->user_key, sizeof(data->user_key));
+ sha256_final(&sha256_state, digest);
+ return crypto_aead_setkey(data->aead_tfm,
+ digest,
+ SNAPSHOT_ENCRYPTION_KEY_SIZE);
+}
+
+/* Check to see if it's time to switch to the user key, and do it if so. */
+static int snapshot_check_user_key_switch(struct snapshot_data *data)
+{
+ if (data->user_key_valid && data->meta_size &&
+ data->crypt_total == data->meta_size) {
+ return snapshot_use_user_key(data);
+ }
+
+ return 0;
+}
+
/* Encrypt more data from the snapshot into the staging area. */
static int snapshot_encrypt_refill(struct snapshot_data *data)
{
@@ -32,6 +65,15 @@ static int snapshot_encrypt_refill(struct snapshot_data *data)
DECLARE_CRYPTO_WAIT(wait);
size_t total = 0;

+ if (data->crypt_total == 0) {
+ data->meta_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+
+ } else {
+ res = snapshot_check_user_key_switch(data);
+ if (res)
+ return res;
+ }
+
/*
* The first buffer is the associated data, set to the offset to prevent
* attacks that rearrange chunks.
@@ -42,6 +84,11 @@ static int snapshot_encrypt_refill(struct snapshot_data *data)
for (pg_idx = 0; pg_idx < CHUNK_SIZE; pg_idx++) {
void *buf = data->crypt_pages[pg_idx];

+ /* Stop at the meta page boundary to potentially switch keys. */
+ if (total &&
+ ((data->crypt_total + total) == data->meta_size))
+ break;
+
res = snapshot_read_next(&data->handle);
if (res < 0)
return res;
@@ -114,10 +161,10 @@ static int snapshot_decrypt_drain(struct snapshot_data *data)
sg_set_buf(&data->sg[1 + pg_idx], data->crypt_pages[pg_idx], PAGE_SIZE);

/*
- * It's possible this is the final decrypt, and there are fewer than
- * CHUNK_SIZE pages. If this is the case we would have just written the
- * auth tag into the first few bytes of a new page. Copy to the tag if
- * so.
+ * It's possible this is the final decrypt, or the final decrypt of the
+ * meta region, and there are fewer than CHUNK_SIZE pages. If this is
+ * the case we would have just written the auth tag into the first few
+ * bytes of a new page. Copy to the tag if so.
*/
if ((page_count < CHUNK_SIZE) &&
(data->crypt_offset - total) == sizeof(data->auth_tag)) {
@@ -172,7 +219,14 @@ static int snapshot_decrypt_drain(struct snapshot_data *data)
total += PAGE_SIZE;
}

+ if (data->crypt_total == 0)
+ data->meta_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+
data->crypt_total += total;
+ res = snapshot_check_user_key_switch(data);
+ if (res)
+ return res;
+
return 0;
}

@@ -221,8 +275,26 @@ static ssize_t snapshot_write_next_encrypted(struct snapshot_data *data,
if (data->crypt_offset < (PAGE_SIZE * CHUNK_SIZE)) {
size_t pg_idx = data->crypt_offset >> PAGE_SHIFT;
size_t pg_off = data->crypt_offset & (PAGE_SIZE - 1);
+ size_t size_avail = PAGE_SIZE;
*buf = data->crypt_pages[pg_idx] + pg_off;
- return PAGE_SIZE - pg_off;
+
+ /*
+ * If this is the boundary where the meta pages end, then just
+ * return enough for the auth tag.
+ */
+ if (data->meta_size && (data->crypt_total < data->meta_size)) {
+ uint64_t total_done =
+ data->crypt_total + data->crypt_offset;
+
+ if ((total_done >= data->meta_size) &&
+ (total_done <
+ (data->meta_size + SNAPSHOT_AUTH_TAG_SIZE))) {
+
+ size_avail = SNAPSHOT_AUTH_TAG_SIZE;
+ }
+ }
+
+ return size_avail - pg_off;
}

/* Use offsets just beyond the size to return the tag. */
@@ -303,9 +375,15 @@ ssize_t snapshot_write_encrypted(struct snapshot_data *data,
break;
}

- /* Drain the encrypted buffer if it's full. */
+ /*
+ * Drain the encrypted buffer if it's full, or if we hit the end
+ * of the meta pages and need a key change.
+ */
if ((data->crypt_offset >=
- ((PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE))) {
+ ((PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE)) ||
+ (data->meta_size && (data->crypt_total < data->meta_size) &&
+ ((data->crypt_total + data->crypt_offset) ==
+ (data->meta_size + SNAPSHOT_AUTH_TAG_SIZE)))) {

int rc;

@@ -345,6 +423,8 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
data->crypt_pages[i] = NULL;
}
}
+
+ memset(data->user_key, 0, sizeof(data->user_key));
}

static int snapshot_setup_encryption_common(struct snapshot_data *data)
@@ -354,6 +434,7 @@ static int snapshot_setup_encryption_common(struct snapshot_data *data)
data->crypt_total = 0;
data->crypt_offset = 0;
data->crypt_size = 0;
+ data->user_key_valid = false;
memset(data->crypt_pages, 0, sizeof(data->crypt_pages));
/* This only works once per hibernate. */
if (data->aead_tfm)
@@ -655,15 +736,72 @@ int snapshot_set_encryption_key(struct snapshot_data *data,
return rc;
}

-loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+static loff_t snapshot_encrypted_byte_count(loff_t plain_size)
{
- loff_t pages = raw_size >> PAGE_SHIFT;
+ loff_t pages = plain_size >> PAGE_SHIFT;
loff_t chunks = (pages + (CHUNK_SIZE - 1)) / CHUNK_SIZE;
/*
* The encrypted size is the normal size, plus a stitched in
* authentication tag for every chunk of pages.
*/
- return raw_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+ return plain_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+}
+
+static loff_t snapshot_get_meta_data_size(void)
+{
+ loff_t pages = snapshot_get_meta_page_count();
+
+ return snapshot_encrypted_byte_count(pages << PAGE_SHIFT);
+}
+
+int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key)
+{
+ struct uswsusp_user_key user_key;
+ unsigned int key_len;
+ int rc;
+ loff_t size;
+
+ /*
+ * Return the metadata size, the number of bytes that can be fed in before
+ * the user data key is needed at resume time.
+ */
+ size = snapshot_get_meta_data_size();
+ rc = put_user(size, &key->meta_size);
+ if (rc)
+ return rc;
+
+ rc = copy_from_user(&user_key, key, sizeof(struct uswsusp_user_key));
+ if (rc)
+ return rc;
+
+ key_len = min_t(__u32, user_key.key_len, sizeof(data->user_key));
+ if (key_len < 8)
+ return -EINVAL;
+
+ /* Don't allow it if it's too late. */
+ if (data->crypt_total > data->meta_size)
+ return -EBUSY;
+
+ memset(data->user_key, 0, sizeof(data->user_key));
+ memcpy(data->user_key, user_key.key, key_len);
+ data->user_key_valid = true;
+ /* Install the key if the user is just under the wire. */
+ rc = snapshot_check_user_key_switch(data);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+{
+ loff_t pages = raw_size >> PAGE_SHIFT;
+ loff_t meta_size;
+
+ pages -= snapshot_get_meta_page_count();
+ meta_size = snapshot_get_meta_data_size();
+ return snapshot_encrypted_byte_count(pages << PAGE_SHIFT) + meta_size;
}

int snapshot_finalize_decrypted_image(struct snapshot_data *data)
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 2a406753af9049..026ee511633bc9 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -2083,6 +2083,11 @@ unsigned long snapshot_get_image_size(void)
return nr_copy_pages + nr_meta_pages + 1;
}

+unsigned long snapshot_get_meta_page_count(void)
+{
+ return nr_meta_pages + 1;
+}
+
static int init_header(struct swsusp_info *info)
{
memset(info, 0, sizeof(struct swsusp_info));
diff --git a/kernel/power/user.c b/kernel/power/user.c
index 348e55f623b7e1..e24fc9bda873a4 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -423,6 +423,10 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = snapshot_set_encryption_key(data, (void __user *)arg);
break;

+ case SNAPSHOT_SET_USER_KEY:
+ error = snapshot_set_user_key(data, (void __user *)arg);
+ break;
+
default:
error = -ENOTTY;

diff --git a/kernel/power/user.h b/kernel/power/user.h
index 591b30bb213349..1b0743b36eee14 100644
--- a/kernel/power/user.h
+++ b/kernel/power/user.h
@@ -32,6 +32,9 @@ struct snapshot_data {
uint64_t nonce_low;
uint64_t nonce_high;
struct key *key;
+ u8 user_key[SNAPSHOT_ENCRYPTION_KEY_SIZE];
+ bool user_key_valid;
+ uint64_t meta_size;
#endif

};
@@ -54,6 +57,9 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
int snapshot_set_encryption_key(struct snapshot_data *data,
struct uswsusp_key_blob __user *key);

+int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key);
+
loff_t snapshot_get_encrypted_image_size(loff_t raw_size);

int snapshot_finalize_decrypted_image(struct snapshot_data *data);
@@ -87,6 +93,12 @@ static int snapshot_set_encryption_key(struct snapshot_data *data,
return -ENOTTY;
}

+static int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key)
+{
+ return -ENOTTY;
+}
+
static loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
{
return raw_size;
--
2.31.0

2022-08-24 12:31:28

by Ben Boeckel

[permalink] [raw]
Subject: Re: [PATCH v2 04/10] security: keys: trusted: Allow storage of PCR values in creation data

On Tue, Aug 23, 2022 at 15:25:20 -0700, Evan Green wrote:
> diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst
> index 0bfb4c33974890..dc9e11bb4824da 100644
> --- a/Documentation/security/keys/trusted-encrypted.rst
> +++ b/Documentation/security/keys/trusted-encrypted.rst
> @@ -199,6 +199,10 @@ Usage::
> policyhandle= handle to an authorization policy session that defines the
> same policy and with the same hash algorithm as was used to
> seal the key.
> + creationpcrs= hex integer representing the set of PCR values to be
> + included in the PCR creation data. The bit corresponding
> + to each PCR should be 1 to be included, 0 to be ignored.
> + TPM2 only.

There's inconsistent whitespace here. Given the context, I suspect the
tabs should be expanded to spaces.

As for the docs themselves, this might preferrably mention how large
this is supposed to be. It seems to be limited to 32bits by the code.
What happens if fewer are provided? More? Will there always be at most
32 PCR values? Also, how are the bits interpreted? I presume bit 0 is
for PCR value 0?

Thanks for including docs.

Thanks,

--Ben

2022-08-24 17:45:03

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 04/10] security: keys: trusted: Allow storage of PCR values in creation data

On Wed, Aug 24, 2022 at 4:56 AM Ben Boeckel <[email protected]> wrote:
>
> On Tue, Aug 23, 2022 at 15:25:20 -0700, Evan Green wrote:
> > diff --git a/Documentation/security/keys/trusted-encrypted.rst b/Documentation/security/keys/trusted-encrypted.rst
> > index 0bfb4c33974890..dc9e11bb4824da 100644
> > --- a/Documentation/security/keys/trusted-encrypted.rst
> > +++ b/Documentation/security/keys/trusted-encrypted.rst
> > @@ -199,6 +199,10 @@ Usage::
> > policyhandle= handle to an authorization policy session that defines the
> > same policy and with the same hash algorithm as was used to
> > seal the key.
> > + creationpcrs= hex integer representing the set of PCR values to be
> > + included in the PCR creation data. The bit corresponding
> > + to each PCR should be 1 to be included, 0 to be ignored.
> > + TPM2 only.
>
> There's inconsistent whitespace here. Given the context, I suspect the
> tabs should be expanded to spaces.
>
> As for the docs themselves, this might preferrably mention how large
> this is supposed to be. It seems to be limited to 32bits by the code.
> What happens if fewer are provided? More? Will there always be at most
> 32 PCR values? Also, how are the bits interpreted? I presume bit 0 is
> for PCR value 0?

Makes sense, I'll pin down the specification a bit better here and fix
up the spacing.

>
> Thanks for including docs.

Thanks for looking at them!

-Evan

2022-08-26 03:12:18

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Tue, Aug 23, 2022 at 03:25:18PM -0700, Evan Green wrote:
> From: Matthew Garrett <[email protected]>
>
> Under certain circumstances it might be desirable to enable the creation
> of TPM-backed secrets that are only accessible to the kernel. In an
> ideal world this could be achieved by using TPM localities, but these
> don't appear to be available on consumer systems. An alternative is to
> simply block userland from modifying one of the resettable PCRs, leaving
> it available to the kernel. If the kernel ensures that no userland can
> access the TPM while it is carrying out work, it can reset PCR 23,
> extend it to an arbitrary value, create or load a secret, and then reset
> the PCR again. Even if userland somehow obtains the sealed material, it
> will be unable to unseal it since PCR 23 will never be in the
> appropriate state.
>
> From: Matthew Garrett <[email protected]>
> Signed-off-by: Matthew Garrett <[email protected]>
>
> Signed-off-by: Evan Green <[email protected]>

Same issues as the other patch.

> ---
> Matthew's original version of this patch is at:
> https://patchwork.kernel.org/patch/12096491/

Suggestion: find a lore URL instead for Matthew's patch,
and then add "Link: <lore URL>" to your commit before
sob's. It's a useful reference also in the commit log.

>
> Changes in v2:
> - Fixed sparse warnings
>
> drivers/char/tpm/Kconfig | 10 +++++++++
> drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> drivers/char/tpm/tpm2-space.c | 2 +-
> 6 files changed, 97 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> index 927088b2c3d3f2..4483b61a428b11 100644
> --- a/drivers/char/tpm/Kconfig
> +++ b/drivers/char/tpm/Kconfig
> @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> This driver proxies for firmware TPM running in TEE.
>
> source "drivers/char/tpm/st33zp24/Kconfig"
> +
> +config TCG_TPM_RESTRICT_PCR
> + bool "Restrict userland access to PCR 23"
> + depends on TCG_TPM
> + help
> + If set, block userland from extending or resetting PCR 23. This
> + allows it to be restricted to in-kernel use, preventing userland
> + from being able to make use of data sealed to the TPM by the kernel.
> + This is required for secure hibernation support, but should be left
> + disabled if any userland may require access to PCR23.
> endif # TCG_TPM
> diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> index dc4c0a0a512903..7a4e618c7d1942 100644
> --- a/drivers/char/tpm/tpm-dev-common.c
> +++ b/drivers/char/tpm/tpm-dev-common.c
> @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> priv->response_read = false;
> *off = 0;
>
> + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> + else
> + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> +
> + if (ret)
> + goto out;
> +
> /*
> * If in nonblocking mode schedule an async job to send
> * the command return the size.
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index a80b341d38eb8c..077c3ca0a127ba 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> int tpm2_probe(struct tpm_chip *chip);
> int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> + const void *buf, size_t bufsiz);
> int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> void tpm_bios_log_teardown(struct tpm_chip *chip);
> int tpm_dev_common_init(void);
> void tpm_dev_common_exit(void);
> +
> +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> +#define TPM_RESTRICTED_PCR 23
> +
> +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> +#else
> +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> + size_t size)
> +{
> + return 0;
> +}
> +
> +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> + size_t size)
> +{
> + return 0;
> +}
> +#endif
> #endif
> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> index 8ec743dec26544..318e75ae42fb85 100644
> --- a/drivers/char/tpm/tpm1-cmd.c
> +++ b/drivers/char/tpm/tpm1-cmd.c
> @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
>
> return 0;
> }
> +
> +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> +{
> + struct tpm_header *header = (struct tpm_header *)buffer;
> + char len, offset;
> + __be32 *pcr;
> + int pos;
> +
> + switch (be32_to_cpu(header->ordinal)) {
> + case TPM_ORD_PCR_EXTEND:
> + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> + return -EINVAL;
> + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> + return -EPERM;
> + break;
> + case TPM_ORD_PCR_RESET:
> + if (size < (TPM_HEADER_SIZE + 1))
> + return -EINVAL;
> + len = buffer[TPM_HEADER_SIZE];
> + if (size < (TPM_HEADER_SIZE + 1 + len))
> + return -EINVAL;
> + offset = TPM_RESTRICTED_PCR/3;
> + if (len < offset)
> + break;
> + pos = TPM_HEADER_SIZE + 1 + offset;
> + if (buffer[pos] & (1 << (TPM_RESTRICTED_PCR - 2 * offset)))
> + return -EPERM;
> + break;
> + }
> +
> + return 0;
> +}
> +#endif
> diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
> index 69126a6770386e..dbf7f5552c6782 100644
> --- a/drivers/char/tpm/tpm2-cmd.c
> +++ b/drivers/char/tpm/tpm2-cmd.c
> @@ -821,3 +821,25 @@ int tpm2_find_cc(struct tpm_chip *chip, u32 cc)
>
> return -1;
> }
> +
> +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> +{
> + int cc = tpm_find_and_validate_cc(chip, NULL, buffer, size);
> + __be32 *handle;
> +
> + switch (cc) {
> + case TPM2_CC_PCR_EXTEND:
> + case TPM2_CC_PCR_RESET:
> + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> + return -EINVAL;
> +
> + handle = (__be32 *)&buffer[TPM_HEADER_SIZE];
> + if (be32_to_cpu(*handle) == TPM_RESTRICTED_PCR)
> + return -EPERM;
> + break;
> + }
> +
> + return 0;
> +}
> +#endif
> diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> index ffb35f0154c16c..6f51cd92c6400f 100644
> --- a/drivers/char/tpm/tpm2-space.c
> +++ b/drivers/char/tpm/tpm2-space.c
> @@ -262,7 +262,7 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd)
> return 0;
> }
>
> -static int tpm_find_and_validate_cc(struct tpm_chip *chip,
> +int tpm_find_and_validate_cc(struct tpm_chip *chip,
> struct tpm_space *space,
> const void *cmd, size_t len)

Split the export to a separate commit.

> {
> --
> 2.31.0
>

BR, Jarkko

2022-08-26 03:14:32

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 01/10] tpm: Add support for in-kernel resetting of PCRs

On Tue, Aug 23, 2022 at 03:25:17PM -0700, Evan Green wrote:
> From: Matthew Garrett <[email protected]>
>
> Add an internal command for resetting a PCR. This will be used by the
> encrypted hibernation code to set PCR23 to a known value. The
> hibernation code will seal the hibernation key with a policy specifying
> PCR23 be set to this known value as a mechanism to ensure that the
> hibernation key is genuine. But to do this repeatedly, resetting the PCR
> is necessary as well.
>
> From: Matthew Garrett <[email protected]>

This is probably here by mistake.

> Signed-off-by: Matthew Garrett <[email protected]>
>

No empty line here.

> Signed-off-by: Evan Green <[email protected]>
> ---
> Matthew's original version of this patch was at:
> https://patchwork.kernel.org/patch/12096487/
>
> (no changes since v1)
>
> drivers/char/tpm/tpm-interface.c | 28 +++++++++++++++++++++++++
> drivers/char/tpm/tpm.h | 2 ++
> drivers/char/tpm/tpm1-cmd.c | 34 ++++++++++++++++++++++++++++++
> drivers/char/tpm/tpm2-cmd.c | 36 ++++++++++++++++++++++++++++++++
> include/linux/tpm.h | 7 +++++++
> 5 files changed, 107 insertions(+)
>
> diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
> index 1621ce8187052c..17b8643ee109c2 100644
> --- a/drivers/char/tpm/tpm-interface.c
> +++ b/drivers/char/tpm/tpm-interface.c
> @@ -342,6 +342,34 @@ int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
> }
> EXPORT_SYMBOL_GPL(tpm_pcr_extend);
>
> +/**
> + * tpm_pcr_reset - reset the specified PCR
> + * @chip: a &struct tpm_chip instance, %NULL for the default chip
> + * @pcr_idx: the PCR to be reset
> + *
> + * Return: same as with tpm_transmit_cmd()
> + */
> +int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx)
> +{
> + int rc;
> +
> + chip = tpm_find_get_ops(chip);
> + if (!chip)
> + return -ENODEV;
> +
> + if (chip->flags & TPM_CHIP_FLAG_TPM2) {
> + rc = tpm2_pcr_reset(chip, pcr_idx);
> + goto out;
> + }
> +
> + rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
> +
> +out:
> + tpm_put_ops(chip);

if (chip->flags & TPM_CHIP_FLAG_TPM2)
rc = tpm2_pcr_reset(chip, pcr_idx);
else
rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");

Where does this asymmetry come with the parameters?

BR, Jarkko

2022-08-31 19:09:10

by Mario Limonciello

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

On 8/23/2022 17:25, Evan Green wrote:
> We are exploring enabling hibernation in some new scenarios. However,
> our security team has a few requirements, listed below:
> 1. The hibernate image must be encrypted with protection derived from
> both the platform (eg TPM) and user authentication data (eg
> password).
> 2. Hibernation must not be a vector by which a malicious userspace can
> escalate to the kernel.
>
> Requirement #1 can be achieved solely with uswsusp, however requirement
> 2 necessitates mechanisms in the kernel to guarantee integrity of the
> hibernate image. The kernel needs a way to authenticate that it generated
> the hibernate image being loaded, and that the image has not been tampered
> with. Adding support for in-kernel AEAD encryption with a TPM-sealed key
> allows us to achieve both requirements with a single computation pass.
>
> Matthew Garrett published a series [1] that aligns closely with this
> goal. His series utilized the fact that PCR23 is a resettable PCR that
> can be blocked from access by usermode. The TPM can create a sealed key
> tied to PCR23 in two ways. First, the TPM can attest to the value of
> PCR23 when the key was created, which the kernel can use on resume to
> verify that the kernel must have created the key (since it is the only
> one capable of modifying PCR23). It can also create a policy that enforces
> PCR23 be set to a specific value as a condition of unsealing the key,
> preventing usermode from unsealing the key by talking directly to the
> TPM.
>
> This series adopts that primitive as a foundation, tweaking and building
> on it a bit. Where Matthew's series used the TPM-backed key to encrypt a
> hash of the image, this series uses the key directly as a gcm(aes)
> encryption key, which the kernel uses to encrypt and decrypt the
> hibernate image in chunks of 16 pages. This provides both encryption and
> integrity, which turns out to be a noticeable performance improvement over
> separate passes for encryption and hashing.
>
> The series also introduces the concept of mixing user key material into
> the encryption key. This allows usermode to introduce key material
> based on unspecified external authentication data (in our case derived
> from something like the user password or PIN), without requiring
> usermode to do a separate encryption pass.
>
> Matthew also documented issues his series had [2] related to generating
> fake images by booting alternate kernels without the PCR23 limiting.
> With access to PCR23 on the same machine, usermode can create fake
> hibernate images that are indistinguishable to the new kernel from
> genuine ones. His post outlines a solution that involves adding more
> PCRs into the creation data and policy, with some gyrations to make this
> work well on a standard PC.
>
> Our approach would be similar: on our machines PCR 0 indicates whether
> the system is booted in secure/verified mode or developer mode. By
> adding PCR0 to the policy, we can reject hibernate images made in
> developer mode while in verified mode (or vice versa).
>
> Additionally, mixing in the user authentication data limits both
> data exfiltration attacks (eg a stolen laptop) and forged hibernation
> image attacks to attackers that already know the authentication data (eg
> user's password). This, combined with our relatively sealed userspace
> (dm-verity on the rootfs), and some judicious clearing of the hibernate
> image (such as across an OS update) further reduce the risk of an online
> attack. The remaining attack space of a forgery from someone with
> physical access to the device and knowledge of the authentication data
> is out of scope for us, given that flipping to developer mode or
> reflashing RO firmware trivially achieves the same thing.
>
> A couple of patches still need to be written on top of this series. The
> generalized functionality to OR in additional PCRs via Kconfig (like PCR
> 0 or 5) still needs to be added. We'll also need a patch that disallows
> unencrypted forms of resume from hibernation, to fully close the door
> to malicious userspace. However, I wanted to get this series out first
> and get reactions from upstream before continuing to add to it.

Something else to think about in this series is what happens with
`hibernation_available` in kernel/power/hibernate.c. Currently if the
system is locked down hibernate is disabled, but I would think that
with a setup like that described here that should no longer be necessary.

>
> [1] https://patchwork.kernel.org/project/linux-pm/cover/[email protected]/
> [2] https://mjg59.dreamwidth.org/58077.html
>
> Changes in v2:
> - Fixed sparse warnings
> - Adjust hash len by 2 due to new ASN.1 storage, and add underflow
> check.
> - Rework load/create_kernel_key() to eliminate a label (Andrey)
> - Call put_device() needed from calling tpm_default_chip().
> - Add missing static on snapshot_encrypted_byte_count()
> - Fold in only the used kernel key bytes to the user key.
> - Make the user key length 32 (Eric)
> - Use CRYPTO_LIB_SHA256 for less boilerplate (Eric)
> - Fixed some sparse warnings
> - Use CRYPTO_LIB_SHA256 to get rid of sha256_data() (Eric)
> - Adjusted offsets due to new ASN.1 format, and added a creation data
> length check.
> - Fix sparse warnings
> - Fix session type comment (Andrey)
> - Eliminate extra label in get/create_kernel_key() (Andrey)
> - Call tpm_try_get_ops() before calling tpm2_flush_context().
>
> Evan Green (7):
> security: keys: trusted: Include TPM2 creation data
> security: keys: trusted: Verify creation data
> PM: hibernate: Add kernel-based encryption
> PM: hibernate: Use TPM-backed keys to encrypt image
> PM: hibernate: Mix user key in encrypted hibernate
> PM: hibernate: Verify the digest encryption key
> PM: hibernate: seal the encryption key with a PCR policy
>
> Matthew Garrett (3):
> tpm: Add support for in-kernel resetting of PCRs
> tpm: Allow PCR 23 to be restricted to kernel-only use
> security: keys: trusted: Allow storage of PCR values in creation data
>
> Documentation/power/userland-swsusp.rst | 8 +
> .../security/keys/trusted-encrypted.rst | 4 +
> drivers/char/tpm/Kconfig | 10 +
> drivers/char/tpm/tpm-dev-common.c | 8 +
> drivers/char/tpm/tpm-interface.c | 28 +
> drivers/char/tpm/tpm.h | 23 +
> drivers/char/tpm/tpm1-cmd.c | 69 ++
> drivers/char/tpm/tpm2-cmd.c | 58 +
> drivers/char/tpm/tpm2-space.c | 2 +-
> include/keys/trusted-type.h | 9 +
> include/linux/tpm.h | 12 +
> include/uapi/linux/suspend_ioctls.h | 28 +-
> kernel/power/Kconfig | 16 +
> kernel/power/Makefile | 1 +
> kernel/power/power.h | 1 +
> kernel/power/snapenc.c | 1037 +++++++++++++++++
> kernel/power/snapshot.c | 5 +
> kernel/power/user.c | 44 +-
> kernel/power/user.h | 114 ++
> security/keys/trusted-keys/tpm2key.asn1 | 5 +-
> security/keys/trusted-keys/trusted_tpm1.c | 9 +
> security/keys/trusted-keys/trusted_tpm2.c | 304 ++++-
> 22 files changed, 1754 insertions(+), 41 deletions(-)
> create mode 100644 kernel/power/snapenc.c
> create mode 100644 kernel/power/user.h
>

2022-09-07 17:06:10

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Thu, Aug 25, 2022 at 8:02 PM Jarkko Sakkinen <[email protected]> wrote:
>
> On Tue, Aug 23, 2022 at 03:25:18PM -0700, Evan Green wrote:
> > From: Matthew Garrett <[email protected]>
> >
> > Under certain circumstances it might be desirable to enable the creation
> > of TPM-backed secrets that are only accessible to the kernel. In an
> > ideal world this could be achieved by using TPM localities, but these
> > don't appear to be available on consumer systems. An alternative is to
> > simply block userland from modifying one of the resettable PCRs, leaving
> > it available to the kernel. If the kernel ensures that no userland can
> > access the TPM while it is carrying out work, it can reset PCR 23,
> > extend it to an arbitrary value, create or load a secret, and then reset
> > the PCR again. Even if userland somehow obtains the sealed material, it
> > will be unable to unseal it since PCR 23 will never be in the
> > appropriate state.
> >
> > From: Matthew Garrett <[email protected]>
> > Signed-off-by: Matthew Garrett <[email protected]>
> >
> > Signed-off-by: Evan Green <[email protected]>
>
> Same issues as the other patch.
>
> > ---
> > Matthew's original version of this patch is at:
> > https://patchwork.kernel.org/patch/12096491/
>
> Suggestion: find a lore URL instead for Matthew's patch,
> and then add "Link: <lore URL>" to your commit before
> sob's. It's a useful reference also in the commit log.
>
> >
> > Changes in v2:
> > - Fixed sparse warnings
> >
> > drivers/char/tpm/Kconfig | 10 +++++++++
> > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > drivers/char/tpm/tpm2-space.c | 2 +-
> > 6 files changed, 97 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > index 927088b2c3d3f2..4483b61a428b11 100644
> > --- a/drivers/char/tpm/Kconfig
> > +++ b/drivers/char/tpm/Kconfig
> > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > This driver proxies for firmware TPM running in TEE.
> >
> > source "drivers/char/tpm/st33zp24/Kconfig"
> > +
> > +config TCG_TPM_RESTRICT_PCR
> > + bool "Restrict userland access to PCR 23"
> > + depends on TCG_TPM
> > + help
> > + If set, block userland from extending or resetting PCR 23. This
> > + allows it to be restricted to in-kernel use, preventing userland
> > + from being able to make use of data sealed to the TPM by the kernel.
> > + This is required for secure hibernation support, but should be left
> > + disabled if any userland may require access to PCR23.
> > endif # TCG_TPM
> > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > index dc4c0a0a512903..7a4e618c7d1942 100644
> > --- a/drivers/char/tpm/tpm-dev-common.c
> > +++ b/drivers/char/tpm/tpm-dev-common.c
> > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > priv->response_read = false;
> > *off = 0;
> >
> > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > + else
> > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > +
> > + if (ret)
> > + goto out;
> > +
> > /*
> > * If in nonblocking mode schedule an async job to send
> > * the command return the size.
> > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > index a80b341d38eb8c..077c3ca0a127ba 100644
> > --- a/drivers/char/tpm/tpm.h
> > +++ b/drivers/char/tpm/tpm.h
> > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > int tpm2_probe(struct tpm_chip *chip);
> > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > + const void *buf, size_t bufsiz);
> > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > int tpm_dev_common_init(void);
> > void tpm_dev_common_exit(void);
> > +
> > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > +#define TPM_RESTRICTED_PCR 23
> > +
> > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > +#else
> > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > + size_t size)
> > +{
> > + return 0;
> > +}
> > +
> > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > + size_t size)
> > +{
> > + return 0;
> > +}
> > +#endif
> > #endif
> > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > index 8ec743dec26544..318e75ae42fb85 100644
> > --- a/drivers/char/tpm/tpm1-cmd.c
> > +++ b/drivers/char/tpm/tpm1-cmd.c
> > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> >
> > return 0;
> > }
> > +
> > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > +{
> > + struct tpm_header *header = (struct tpm_header *)buffer;
> > + char len, offset;
> > + __be32 *pcr;
> > + int pos;
> > +
> > + switch (be32_to_cpu(header->ordinal)) {
> > + case TPM_ORD_PCR_EXTEND:
> > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > + return -EINVAL;
> > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > + return -EPERM;
> > + break;
> > + case TPM_ORD_PCR_RESET:
> > + if (size < (TPM_HEADER_SIZE + 1))
> > + return -EINVAL;
> > + len = buffer[TPM_HEADER_SIZE];
> > + if (size < (TPM_HEADER_SIZE + 1 + len))
> > + return -EINVAL;
> > + offset = TPM_RESTRICTED_PCR/3;
> > + if (len < offset)
> > + break;
> > + pos = TPM_HEADER_SIZE + 1 + offset;
> > + if (buffer[pos] & (1 << (TPM_RESTRICTED_PCR - 2 * offset)))
> > + return -EPERM;
> > + break;
> > + }
> > +
> > + return 0;
> > +}
> > +#endif
> > diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
> > index 69126a6770386e..dbf7f5552c6782 100644
> > --- a/drivers/char/tpm/tpm2-cmd.c
> > +++ b/drivers/char/tpm/tpm2-cmd.c
> > @@ -821,3 +821,25 @@ int tpm2_find_cc(struct tpm_chip *chip, u32 cc)
> >
> > return -1;
> > }
> > +
> > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > +{
> > + int cc = tpm_find_and_validate_cc(chip, NULL, buffer, size);
> > + __be32 *handle;
> > +
> > + switch (cc) {
> > + case TPM2_CC_PCR_EXTEND:
> > + case TPM2_CC_PCR_RESET:
> > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > + return -EINVAL;
> > +
> > + handle = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > + if (be32_to_cpu(*handle) == TPM_RESTRICTED_PCR)
> > + return -EPERM;
> > + break;
> > + }
> > +
> > + return 0;
> > +}
> > +#endif
> > diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> > index ffb35f0154c16c..6f51cd92c6400f 100644
> > --- a/drivers/char/tpm/tpm2-space.c
> > +++ b/drivers/char/tpm/tpm2-space.c
> > @@ -262,7 +262,7 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd)
> > return 0;
> > }
> >
> > -static int tpm_find_and_validate_cc(struct tpm_chip *chip,
> > +int tpm_find_and_validate_cc(struct tpm_chip *chip,
> > struct tpm_space *space,
> > const void *cmd, size_t len)
>
> Split the export to a separate commit.

Will do all these, thanks for taking a look.

2022-09-07 17:13:59

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 01/10] tpm: Add support for in-kernel resetting of PCRs

On Thu, Aug 25, 2022 at 8:00 PM Jarkko Sakkinen <[email protected]> wrote:
>
> On Tue, Aug 23, 2022 at 03:25:17PM -0700, Evan Green wrote:
> > From: Matthew Garrett <[email protected]>
> >
> > Add an internal command for resetting a PCR. This will be used by the
> > encrypted hibernation code to set PCR23 to a known value. The
> > hibernation code will seal the hibernation key with a policy specifying
> > PCR23 be set to this known value as a mechanism to ensure that the
> > hibernation key is genuine. But to do this repeatedly, resetting the PCR
> > is necessary as well.
> >
> > From: Matthew Garrett <[email protected]>
>
> This is probably here by mistake.
>
> > Signed-off-by: Matthew Garrett <[email protected]>
> >
>
> No empty line here.
>
> > Signed-off-by: Evan Green <[email protected]>
> > ---
> > Matthew's original version of this patch was at:
> > https://patchwork.kernel.org/patch/12096487/
> >
> > (no changes since v1)
> >
> > drivers/char/tpm/tpm-interface.c | 28 +++++++++++++++++++++++++
> > drivers/char/tpm/tpm.h | 2 ++
> > drivers/char/tpm/tpm1-cmd.c | 34 ++++++++++++++++++++++++++++++
> > drivers/char/tpm/tpm2-cmd.c | 36 ++++++++++++++++++++++++++++++++
> > include/linux/tpm.h | 7 +++++++
> > 5 files changed, 107 insertions(+)
> >
> > diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
> > index 1621ce8187052c..17b8643ee109c2 100644
> > --- a/drivers/char/tpm/tpm-interface.c
> > +++ b/drivers/char/tpm/tpm-interface.c
> > @@ -342,6 +342,34 @@ int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
> > }
> > EXPORT_SYMBOL_GPL(tpm_pcr_extend);
> >
> > +/**
> > + * tpm_pcr_reset - reset the specified PCR
> > + * @chip: a &struct tpm_chip instance, %NULL for the default chip
> > + * @pcr_idx: the PCR to be reset
> > + *
> > + * Return: same as with tpm_transmit_cmd()
> > + */
> > +int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx)
> > +{
> > + int rc;
> > +
> > + chip = tpm_find_get_ops(chip);
> > + if (!chip)
> > + return -ENODEV;
> > +
> > + if (chip->flags & TPM_CHIP_FLAG_TPM2) {
> > + rc = tpm2_pcr_reset(chip, pcr_idx);
> > + goto out;
> > + }
> > +
> > + rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
> > +
> > +out:
> > + tpm_put_ops(chip);
>
> if (chip->flags & TPM_CHIP_FLAG_TPM2)
> rc = tpm2_pcr_reset(chip, pcr_idx);
> else
> rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
>
> Where does this asymmetry come with the parameters?

Sorry for the delay, I was out last week. I think it's modeled to
match the tpm1/2_pcr_extend functions, which have this same odd
asymmetry. Should I change it to have both use the tpm2_pcr_reset()
prototype?
-Evan

2022-09-07 17:46:08

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

On Wed, Aug 31, 2022 at 11:35 AM Limonciello, Mario
<[email protected]> wrote:
>
> On 8/23/2022 17:25, Evan Green wrote:
> > We are exploring enabling hibernation in some new scenarios. However,
> > our security team has a few requirements, listed below:
> > 1. The hibernate image must be encrypted with protection derived from
> > both the platform (eg TPM) and user authentication data (eg
> > password).
> > 2. Hibernation must not be a vector by which a malicious userspace can
> > escalate to the kernel.
> >
> > Requirement #1 can be achieved solely with uswsusp, however requirement
> > 2 necessitates mechanisms in the kernel to guarantee integrity of the
> > hibernate image. The kernel needs a way to authenticate that it generated
> > the hibernate image being loaded, and that the image has not been tampered
> > with. Adding support for in-kernel AEAD encryption with a TPM-sealed key
> > allows us to achieve both requirements with a single computation pass.
> >
> > Matthew Garrett published a series [1] that aligns closely with this
> > goal. His series utilized the fact that PCR23 is a resettable PCR that
> > can be blocked from access by usermode. The TPM can create a sealed key
> > tied to PCR23 in two ways. First, the TPM can attest to the value of
> > PCR23 when the key was created, which the kernel can use on resume to
> > verify that the kernel must have created the key (since it is the only
> > one capable of modifying PCR23). It can also create a policy that enforces
> > PCR23 be set to a specific value as a condition of unsealing the key,
> > preventing usermode from unsealing the key by talking directly to the
> > TPM.
> >
> > This series adopts that primitive as a foundation, tweaking and building
> > on it a bit. Where Matthew's series used the TPM-backed key to encrypt a
> > hash of the image, this series uses the key directly as a gcm(aes)
> > encryption key, which the kernel uses to encrypt and decrypt the
> > hibernate image in chunks of 16 pages. This provides both encryption and
> > integrity, which turns out to be a noticeable performance improvement over
> > separate passes for encryption and hashing.
> >
> > The series also introduces the concept of mixing user key material into
> > the encryption key. This allows usermode to introduce key material
> > based on unspecified external authentication data (in our case derived
> > from something like the user password or PIN), without requiring
> > usermode to do a separate encryption pass.
> >
> > Matthew also documented issues his series had [2] related to generating
> > fake images by booting alternate kernels without the PCR23 limiting.
> > With access to PCR23 on the same machine, usermode can create fake
> > hibernate images that are indistinguishable to the new kernel from
> > genuine ones. His post outlines a solution that involves adding more
> > PCRs into the creation data and policy, with some gyrations to make this
> > work well on a standard PC.
> >
> > Our approach would be similar: on our machines PCR 0 indicates whether
> > the system is booted in secure/verified mode or developer mode. By
> > adding PCR0 to the policy, we can reject hibernate images made in
> > developer mode while in verified mode (or vice versa).
> >
> > Additionally, mixing in the user authentication data limits both
> > data exfiltration attacks (eg a stolen laptop) and forged hibernation
> > image attacks to attackers that already know the authentication data (eg
> > user's password). This, combined with our relatively sealed userspace
> > (dm-verity on the rootfs), and some judicious clearing of the hibernate
> > image (such as across an OS update) further reduce the risk of an online
> > attack. The remaining attack space of a forgery from someone with
> > physical access to the device and knowledge of the authentication data
> > is out of scope for us, given that flipping to developer mode or
> > reflashing RO firmware trivially achieves the same thing.
> >
> > A couple of patches still need to be written on top of this series. The
> > generalized functionality to OR in additional PCRs via Kconfig (like PCR
> > 0 or 5) still needs to be added. We'll also need a patch that disallows
> > unencrypted forms of resume from hibernation, to fully close the door
> > to malicious userspace. However, I wanted to get this series out first
> > and get reactions from upstream before continuing to add to it.
>
> Something else to think about in this series is what happens with
> `hibernation_available` in kernel/power/hibernate.c. Currently if the
> system is locked down hibernate is disabled, but I would think that
> with a setup like that described here that should no longer be necessary.
>

Correct, I think that would be a reasonable followup to this series.

-Evan

2022-09-08 05:41:51

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 01/10] tpm: Add support for in-kernel resetting of PCRs

On Wed, Sep 07, 2022 at 10:02:14AM -0700, Evan Green wrote:
> On Thu, Aug 25, 2022 at 8:00 PM Jarkko Sakkinen <[email protected]> wrote:
> >
> > On Tue, Aug 23, 2022 at 03:25:17PM -0700, Evan Green wrote:
> > > From: Matthew Garrett <[email protected]>
> > >
> > > Add an internal command for resetting a PCR. This will be used by the
> > > encrypted hibernation code to set PCR23 to a known value. The
> > > hibernation code will seal the hibernation key with a policy specifying
> > > PCR23 be set to this known value as a mechanism to ensure that the
> > > hibernation key is genuine. But to do this repeatedly, resetting the PCR
> > > is necessary as well.
> > >
> > > From: Matthew Garrett <[email protected]>
> >
> > This is probably here by mistake.
> >
> > > Signed-off-by: Matthew Garrett <[email protected]>
> > >
> >
> > No empty line here.
> >
> > > Signed-off-by: Evan Green <[email protected]>
> > > ---
> > > Matthew's original version of this patch was at:
> > > https://patchwork.kernel.org/patch/12096487/
> > >
> > > (no changes since v1)
> > >
> > > drivers/char/tpm/tpm-interface.c | 28 +++++++++++++++++++++++++
> > > drivers/char/tpm/tpm.h | 2 ++
> > > drivers/char/tpm/tpm1-cmd.c | 34 ++++++++++++++++++++++++++++++
> > > drivers/char/tpm/tpm2-cmd.c | 36 ++++++++++++++++++++++++++++++++
> > > include/linux/tpm.h | 7 +++++++
> > > 5 files changed, 107 insertions(+)
> > >
> > > diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
> > > index 1621ce8187052c..17b8643ee109c2 100644
> > > --- a/drivers/char/tpm/tpm-interface.c
> > > +++ b/drivers/char/tpm/tpm-interface.c
> > > @@ -342,6 +342,34 @@ int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
> > > }
> > > EXPORT_SYMBOL_GPL(tpm_pcr_extend);
> > >
> > > +/**
> > > + * tpm_pcr_reset - reset the specified PCR
> > > + * @chip: a &struct tpm_chip instance, %NULL for the default chip
> > > + * @pcr_idx: the PCR to be reset
> > > + *
> > > + * Return: same as with tpm_transmit_cmd()
> > > + */
> > > +int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx)
> > > +{
> > > + int rc;
> > > +
> > > + chip = tpm_find_get_ops(chip);
> > > + if (!chip)
> > > + return -ENODEV;
> > > +
> > > + if (chip->flags & TPM_CHIP_FLAG_TPM2) {
> > > + rc = tpm2_pcr_reset(chip, pcr_idx);
> > > + goto out;
> > > + }
> > > +
> > > + rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
> > > +
> > > +out:
> > > + tpm_put_ops(chip);
> >
> > if (chip->flags & TPM_CHIP_FLAG_TPM2)
> > rc = tpm2_pcr_reset(chip, pcr_idx);
> > else
> > rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR");
> >
> > Where does this asymmetry come with the parameters?
>
> Sorry for the delay, I was out last week. I think it's modeled to
> match the tpm1/2_pcr_extend functions, which have this same odd
> asymmetry. Should I change it to have both use the tpm2_pcr_reset()
> prototype?
> -Evan

Yeah, I think it'd be a good idea.

BR, Jarkko

2022-09-13 13:35:45

by Stefan Berger

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use



On 8/23/22 18:25, Evan Green wrote:
> From: Matthew Garrett <[email protected]>
>
> Under certain circumstances it might be desirable to enable the creation
> of TPM-backed secrets that are only accessible to the kernel. In an
> ideal world this could be achieved by using TPM localities, but these
> don't appear to be available on consumer systems. An alternative is to
> simply block userland from modifying one of the resettable PCRs, leaving
> it available to the kernel. If the kernel ensures that no userland can
> access the TPM while it is carrying out work, it can reset PCR 23,
> extend it to an arbitrary value, create or load a secret, and then reset
> the PCR again. Even if userland somehow obtains the sealed material, it
> will be unable to unseal it since PCR 23 will never be in the
> appropriate state.
>
> From: Matthew Garrett <[email protected]>
> Signed-off-by: Matthew Garrett <[email protected]>
>
> Signed-off-by: Evan Green <[email protected]>
> ---
> Matthew's original version of this patch is at:
> https://patchwork.kernel.org/patch/12096491/
>
> Changes in v2:
> - Fixed sparse warnings
>
> drivers/char/tpm/Kconfig | 10 +++++++++
> drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> drivers/char/tpm/tpm2-space.c | 2 +-
> 6 files changed, 97 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> index 927088b2c3d3f2..4483b61a428b11 100644
> --- a/drivers/char/tpm/Kconfig
> +++ b/drivers/char/tpm/Kconfig
> @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> This driver proxies for firmware TPM running in TEE.
>
> source "drivers/char/tpm/st33zp24/Kconfig"
> +
> +config TCG_TPM_RESTRICT_PCR
> + bool "Restrict userland access to PCR 23"
> + depends on TCG_TPM
> + help
> + If set, block userland from extending or resetting PCR 23. This
> + allows it to be restricted to in-kernel use, preventing userland
> + from being able to make use of data sealed to the TPM by the kernel.
> + This is required for secure hibernation support, but should be left
> + disabled if any userland may require access to PCR23.
> endif # TCG_TPM
> diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> index dc4c0a0a512903..7a4e618c7d1942 100644
> --- a/drivers/char/tpm/tpm-dev-common.c
> +++ b/drivers/char/tpm/tpm-dev-common.c
> @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> priv->response_read = false;
> *off = 0;
>
> + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> + else
> + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> +
> + if (ret)
> + goto out;
> +
> /*
> * If in nonblocking mode schedule an async job to send
> * the command return the size.
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index a80b341d38eb8c..077c3ca0a127ba 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> int tpm2_probe(struct tpm_chip *chip);
> int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> + const void *buf, size_t bufsiz);
> int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> void tpm_bios_log_teardown(struct tpm_chip *chip);
> int tpm_dev_common_init(void);
> void tpm_dev_common_exit(void);
> +
> +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> +#define TPM_RESTRICTED_PCR 23
> +
> +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> +#else
> +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> + size_t size)
> +{
> + return 0;
> +}
> +
> +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> + size_t size)
> +{
> + return 0;
> +}
> +#endif
> #endif
> diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> index 8ec743dec26544..318e75ae42fb85 100644
> --- a/drivers/char/tpm/tpm1-cmd.c
> +++ b/drivers/char/tpm/tpm1-cmd.c
> @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
>
> return 0;
> }
> +
> +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> +{
> + struct tpm_header *header = (struct tpm_header *)buffer;
> + char len, offset;
> + __be32 *pcr;
> + int pos;
> +
> + switch (be32_to_cpu(header->ordinal)) {
> + case TPM_ORD_PCR_EXTEND:
> + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> + return -EINVAL;
> + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> + return -EPERM;

FYI: TPM 1.2 has transport sessions where the command is tunneled in an
encrypted channel and this check could be circumvented...

2022-09-20 05:21:50

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Tue, Sep 13, 2022 at 08:26:09AM -0400, Stefan Berger wrote:
>
>
> On 8/23/22 18:25, Evan Green wrote:
> > From: Matthew Garrett <[email protected]>
> >
> > Under certain circumstances it might be desirable to enable the creation
> > of TPM-backed secrets that are only accessible to the kernel. In an
> > ideal world this could be achieved by using TPM localities, but these
> > don't appear to be available on consumer systems. An alternative is to
> > simply block userland from modifying one of the resettable PCRs, leaving
> > it available to the kernel. If the kernel ensures that no userland can
> > access the TPM while it is carrying out work, it can reset PCR 23,
> > extend it to an arbitrary value, create or load a secret, and then reset
> > the PCR again. Even if userland somehow obtains the sealed material, it
> > will be unable to unseal it since PCR 23 will never be in the
> > appropriate state.
> >
> > From: Matthew Garrett <[email protected]>
> > Signed-off-by: Matthew Garrett <[email protected]>
> >
> > Signed-off-by: Evan Green <[email protected]>
> > ---
> > Matthew's original version of this patch is at:
> > https://patchwork.kernel.org/patch/12096491/
> >
> > Changes in v2:
> > - Fixed sparse warnings
> >
> > drivers/char/tpm/Kconfig | 10 +++++++++
> > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > drivers/char/tpm/tpm2-space.c | 2 +-
> > 6 files changed, 97 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > index 927088b2c3d3f2..4483b61a428b11 100644
> > --- a/drivers/char/tpm/Kconfig
> > +++ b/drivers/char/tpm/Kconfig
> > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > This driver proxies for firmware TPM running in TEE.
> > source "drivers/char/tpm/st33zp24/Kconfig"
> > +
> > +config TCG_TPM_RESTRICT_PCR
> > + bool "Restrict userland access to PCR 23"
> > + depends on TCG_TPM
> > + help
> > + If set, block userland from extending or resetting PCR 23. This
> > + allows it to be restricted to in-kernel use, preventing userland
> > + from being able to make use of data sealed to the TPM by the kernel.
> > + This is required for secure hibernation support, but should be left
> > + disabled if any userland may require access to PCR23.
> > endif # TCG_TPM
> > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > index dc4c0a0a512903..7a4e618c7d1942 100644
> > --- a/drivers/char/tpm/tpm-dev-common.c
> > +++ b/drivers/char/tpm/tpm-dev-common.c
> > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > priv->response_read = false;
> > *off = 0;
> > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > + else
> > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > +
> > + if (ret)
> > + goto out;
> > +
> > /*
> > * If in nonblocking mode schedule an async job to send
> > * the command return the size.
> > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > index a80b341d38eb8c..077c3ca0a127ba 100644
> > --- a/drivers/char/tpm/tpm.h
> > +++ b/drivers/char/tpm/tpm.h
> > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > int tpm2_probe(struct tpm_chip *chip);
> > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > + const void *buf, size_t bufsiz);
> > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > int tpm_dev_common_init(void);
> > void tpm_dev_common_exit(void);
> > +
> > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > +#define TPM_RESTRICTED_PCR 23
> > +
> > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > +#else
> > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > + size_t size)
> > +{
> > + return 0;
> > +}
> > +
> > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > + size_t size)
> > +{
> > + return 0;
> > +}
> > +#endif
> > #endif
> > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > index 8ec743dec26544..318e75ae42fb85 100644
> > --- a/drivers/char/tpm/tpm1-cmd.c
> > +++ b/drivers/char/tpm/tpm1-cmd.c
> > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> > return 0;
> > }
> > +
> > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > +{
> > + struct tpm_header *header = (struct tpm_header *)buffer;
> > + char len, offset;
> > + __be32 *pcr;
> > + int pos;
> > +
> > + switch (be32_to_cpu(header->ordinal)) {
> > + case TPM_ORD_PCR_EXTEND:
> > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > + return -EINVAL;
> > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > + return -EPERM;
>
> FYI: TPM 1.2 has transport sessions where the command is tunneled in an
> encrypted channel and this check could be circumvented...

BTW, Why do we want to support TPM 1.2 at all.

I would not support it for new features. This could be just TPM2 only
feeature.

BR, Jarkko

2022-09-20 09:21:31

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

Hi!

> We are exploring enabling hibernation in some new scenarios. However,
> our security team has a few requirements, listed below:
> 1. The hibernate image must be encrypted with protection derived from
> both the platform (eg TPM) and user authentication data (eg
> password).
> 2. Hibernation must not be a vector by which a malicious userspace can
> escalate to the kernel.

Why is #2 reasonable requirement?

We normally allow userspace with appropriate permissions to update the
kernel, for example.

Best regards,
Pavel
--
People of Russia, stop Putin before his war on Ukraine escalates.


Attachments:
(No filename) (636.00 B)
signature.asc (201.00 B)
Download all attachments

2022-09-20 16:52:04

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

On Tue, Sep 20, 2022 at 1:46 AM Pavel Machek <[email protected]> wrote:
>
> Hi!
>
> > We are exploring enabling hibernation in some new scenarios. However,
> > our security team has a few requirements, listed below:
> > 1. The hibernate image must be encrypted with protection derived from
> > both the platform (eg TPM) and user authentication data (eg
> > password).
> > 2. Hibernation must not be a vector by which a malicious userspace can
> > escalate to the kernel.
>
> Why is #2 reasonable requirement?
>
> We normally allow userspace with appropriate permissions to update the
> kernel, for example.

I'll take a stab at answering this. I've also CCed one of our security
folks to keep me honest and add any needed additional context.

ChromeOS takes an approach of attempting to limit the blast radius of
any given vulnerability as much as possible. A vulnerable system
service may be running as root, but may also still be fairly
constrained by sandboxing: it may not have access to all processes,
the entire file system, or all capability bits. With Verified Boot
[1], our kernel and rootfs are statically signed by Google (or
yourself if firmware has been reflashed). Even if a full root
compromise occurs, it's difficult for the attacker to persist across a
reboot, since they cannot update the kernel or init flow on disk
without the signing key.

We do our best to lock down other escalation vectors from root to
kernel as well. For instance, features like LoadPin help prevent a
malicious root from simply loading up a payload via insmod.

So in cases like ours, jumping from root execution to kernel execution
represents a real escalation in privilege. Hibernate as it exists
today represents a wide open door for root to become kernel, so we're
forced to disable the Kconfigs for it. This series, along with another
patch to restrict unencrypted resume, would add the guardrails we need
to prevent arbitrary code from moving into the kernel via resume.

-Evan

[1] https://www.chromium.org/chromium-os/chromiumos-design-docs/verified-boot/

2022-09-20 23:22:21

by Kees Cook

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

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 <[email protected]>
> ---
> 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

2022-09-20 23:25:09

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 05/10] security: keys: trusted: Verify creation data

On Tue, Aug 23, 2022 at 03:25:21PM -0700, Evan Green wrote:
> If a loaded key contains creation data, ask the TPM to verify that
> creation data. This allows users like encrypted hibernate to know that
> the loaded and parsed creation data has not been tampered with.
>
> Partially-sourced-from: Matthew Garrett <[email protected]>
> Signed-off-by: Evan Green <[email protected]>
>
> ---
> Source material for this change is at:
> https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/
>
> Changes in v2:
> - Adjust hash len by 2 due to new ASN.1 storage, and add underflow
> check.
>
> include/linux/tpm.h | 1 +
> security/keys/trusted-keys/trusted_tpm2.c | 77 ++++++++++++++++++++++-
> 2 files changed, 77 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 8320cbac6f4009..438f8bc0a50582 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -224,6 +224,7 @@ enum tpm2_command_codes {
> TPM2_CC_SELF_TEST = 0x0143,
> TPM2_CC_STARTUP = 0x0144,
> TPM2_CC_SHUTDOWN = 0x0145,
> + TPM2_CC_CERTIFYCREATION = 0x014A,
> TPM2_CC_NV_READ = 0x014E,
> TPM2_CC_CREATE = 0x0153,
> TPM2_CC_LOAD = 0x0157,
> diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
> index 1d1470b880ca01..f81c6578c7f783 100644
> --- a/security/keys/trusted-keys/trusted_tpm2.c
> +++ b/security/keys/trusted-keys/trusted_tpm2.c
> @@ -691,6 +691,74 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> return rc;
> }
>
> +/**
> + * tpm2_certify_creation() - execute a TPM2_CertifyCreation command
> + *
> + * @chip: TPM chip to use
> + * @payload: the key data in clear and encrypted form
> + * @blob_handle: the loaded TPM handle of the key
> + *
> + * Return: 0 on success
> + * -EINVAL on tpm error status
> + * < 0 error from tpm_send or tpm_buf_init
> + */
> +static int tpm2_certify_creation(struct tpm_chip *chip,
> + struct trusted_key_payload *payload,
> + u32 blob_handle)
> +{
> + struct tpm_header *head;
> + struct tpm_buf buf;
> + int rc;
> +
> + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CERTIFYCREATION);
> + if (rc)
> + return rc;
> +
> + /* Use TPM_RH_NULL for signHandle */
> + tpm_buf_append_u32(&buf, 0x40000007);
> +
> + /* Object handle */
> + tpm_buf_append_u32(&buf, blob_handle);
> +
> + /* Auth */
> + tpm_buf_append_u32(&buf, 9);
> + tpm_buf_append_u32(&buf, TPM2_RS_PW);
> + tpm_buf_append_u16(&buf, 0);
> + tpm_buf_append_u8(&buf, 0);
> + tpm_buf_append_u16(&buf, 0);
> +
> + /* Qualifying data */
> + tpm_buf_append_u16(&buf, 0);
> +
> + /* Creation data hash */
> + if (payload->creation_hash_len < 2) {
> + rc = -EINVAL;
> + goto out;
> + }
> +
> + tpm_buf_append_u16(&buf, payload->creation_hash_len - 2);
> + tpm_buf_append(&buf, payload->creation_hash + 2,
> + payload->creation_hash_len - 2);
> +
> + /* signature scheme */
> + tpm_buf_append_u16(&buf, TPM_ALG_NULL);
> +
> + /* creation ticket */
> + tpm_buf_append(&buf, payload->tk, payload->tk_len);
> +
> + rc = tpm_transmit_cmd(chip, &buf, 6, "certifying creation data");
> + if (rc)
> + goto out;
> +
> + head = (struct tpm_header *)buf.data;
> +
> + if (head->return_code != 0)
> + rc = -EINVAL;

Do you have a reference to this TPM command spec? I have a dim memory of
some of these commands having success/failure listed separately from
other things in the reply. Is that true here? (i.e. is the return_code
only about "yes I replied" and there is a missing "but the answer is no"
check?)

> +out:
> + tpm_buf_destroy(&buf);
> + return rc;
> +}
> +
> /**
> * tpm2_unseal_trusted() - unseal the payload of a trusted key
> *
> @@ -716,8 +784,15 @@ int tpm2_unseal_trusted(struct tpm_chip *chip,
> goto out;
>
> rc = tpm2_unseal_cmd(chip, payload, options, blob_handle);
> - tpm2_flush_context(chip, blob_handle);
> + if (rc)
> + goto flush;
> +
> + if (payload->creation_len)
> + rc = tpm2_certify_creation(chip, payload, blob_handle);
>
> +
> +flush:
> + tpm2_flush_context(chip, blob_handle);
> out:
> tpm_put_ops(chip);
>
> --
> 2.31.0
>

Otherwise looks good to me. :)

--
Kees Cook

2022-09-20 23:25:31

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

On Tue, Aug 23, 2022 at 03:25:16PM -0700, Evan Green wrote:
> This series adopts that primitive as a foundation, tweaking and building
> on it a bit. Where Matthew's series used the TPM-backed key to encrypt a
> hash of the image, this series uses the key directly as a gcm(aes)
> encryption key, which the kernel uses to encrypt and decrypt the
> hibernate image in chunks of 16 pages. This provides both encryption and
> integrity, which turns out to be a noticeable performance improvement over
> separate passes for encryption and hashing.

I like this series! I would ask that someone more familiar with the
cryptographic constraints here confirm that the primitives you're using
are going to actually provide you the constraints you want (i.e.
encryption, integrity, etc). My understanding is that gcm(aes) is
exactly right, but I Am Not A Cryptographer. ;)

I'll reply more to individual patches ...

--
Kees Cook

2022-09-20 23:42:49

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] PM: hibernate: Add kernel-based encryption

On Tue, Aug 23, 2022 at 03:25:22PM -0700, Evan Green wrote:
> Enabling the kernel to be able to do encryption and integrity checks on
> the hibernate image prevents a malicious userspace from escalating to
> kernel execution via hibernation resume. As a first step toward this, add
> the scaffolding needed for the kernel to do AEAD encryption on the
> hibernate image, giving us both secrecy and integrity.

I'd love Eric to take a look at this, just to make sure the crypto API
is being used correctly here. :)

--
Kees Cook

2022-09-20 23:45:16

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 07/10] PM: hibernate: Use TPM-backed keys to encrypt image

On Tue, Aug 23, 2022 at 03:25:23PM -0700, Evan Green wrote:
> When using encrypted hibernate images, have the TPM create a key for us
> and seal it. By handing back a sealed blob instead of the raw key, we
> prevent usermode from being able to decrypt and tamper with the
> hibernate image on a different machine.
>
> We'll also go through the motions of having PCR23 set to a known value at
> the time of key creation and unsealing. Currently there's nothing that
> enforces the contents of PCR23 as a condition to unseal the key blob,
> that will come in a later change.
>
> Sourced-from: Matthew Garrett <[email protected]>
> Signed-off-by: Evan Green <[email protected]>
>
> ---
> Matthew's incarnation of this patch is at:
> https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/
>
> Changes in v2:
> - Rework load/create_kernel_key() to eliminate a label (Andrey)
> - Call put_device() needed from calling tpm_default_chip().
>
> kernel/power/Kconfig | 2 +
> kernel/power/snapenc.c | 207 +++++++++++++++++++++++++++++++++++++++--
> kernel/power/user.h | 1 +
> 3 files changed, 201 insertions(+), 9 deletions(-)
>
> diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> index cd574af0b43379..ff15361c5d8fad 100644
> --- a/kernel/power/Kconfig
> +++ b/kernel/power/Kconfig
> @@ -96,6 +96,8 @@ config ENCRYPTED_HIBERNATION
> bool "Encryption support for userspace snapshots"
> depends on HIBERNATION_SNAPSHOT_DEV
> depends on CRYPTO_AEAD2=y
> + depends on KEYS
> + depends on TRUSTED_KEYS

nit: TRUSTED_KEYS already depends on KEYS, so the KEYS depend can be
dropped.

> default n
> help
> Enable support for kernel-based encryption of hibernation snapshots
> diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
> index cb90692d6ab83a..90079f6d4f184b 100644
> --- a/kernel/power/snapenc.c
> +++ b/kernel/power/snapenc.c
> @@ -4,13 +4,23 @@
> #include <linux/crypto.h>
> #include <crypto/aead.h>
> #include <crypto/gcm.h>
> +#include <keys/trusted-type.h>
> +#include <linux/key-type.h>
> #include <linux/random.h>
> #include <linux/mm.h>
> +#include <linux/tpm.h>
> #include <linux/uaccess.h>
>
> #include "power.h"
> #include "user.h"
>
> +/* sha256("To sleep, perchance to dream") */
> +static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
> + .digest = {0x92, 0x78, 0x3d, 0x79, 0x2d, 0x00, 0x31, 0xb0, 0x55, 0xf9,
> + 0x1e, 0x0d, 0xce, 0x83, 0xde, 0x1d, 0xc4, 0xc5, 0x8e, 0x8c,
> + 0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
> + 0x5f, 0x49}};
> +
> /* Encrypt more data from the snapshot into the staging area. */
> static int snapshot_encrypt_refill(struct snapshot_data *data)
> {
> @@ -313,6 +323,12 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
> {
> int i;
>
> + if (data->key) {
> + key_revoke(data->key);
> + key_put(data->key);
> + data->key = NULL;
> + }
> +
> if (data->aead_req) {
> aead_request_free(data->aead_req);
> data->aead_req = NULL;
> @@ -381,11 +397,83 @@ static int snapshot_setup_encryption_common(struct snapshot_data *data)
> return rc;
> }
>
> +static int snapshot_create_kernel_key(struct snapshot_data *data)
> +{
> + const struct cred *cred = current_cred();
> + struct tpm_digest *digests = NULL;
> + struct tpm_chip *chip;
> + struct key *key = NULL;
> + int ret, i;
> + /* Create a key sealed by the SRK. */
> + char *keyinfo = "new\t32\tkeyhandle=0x81000000";
> +
> + chip = tpm_default_chip();
> + if (!chip)
> + return -ENODEV;
> +
> + if (!(tpm_is_tpm2(chip))) {
> + ret = -ENODEV;
> + goto out_dev;
> + }
> +
> + ret = tpm_pcr_reset(chip, 23);
> + if (ret)
> + goto out;
> +
> + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
> + GFP_KERNEL);
> + if (!digests) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + for (i = 0; i <= chip->nr_allocated_banks; i++) {
> + digests[i].alg_id = chip->allocated_banks[i].alg_id;
> + if (digests[i].alg_id == known_digest.alg_id)
> + memcpy(&digests[i], &known_digest, sizeof(known_digest));
> + }
> +
> + ret = tpm_pcr_extend(chip, 23, digests);
> + if (ret != 0)
> + goto out;
> +
> + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
> + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
> + NULL);
> +
> + if (IS_ERR(key)) {
> + ret = PTR_ERR(key);
> + key = NULL;
> + goto out;
> + }
> +
> + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
> + NULL);
> + if (ret != 0)
> + goto out;
> +
> + data->key = key;
> + key = NULL;
> +
> +out:
> + if (key) {
> + key_revoke(key);
> + key_put(key);
> + }
> +
> + kfree(digests);
> + tpm_pcr_reset(chip, 23);
> +
> +out_dev:
> + put_device(&chip->dev);
> + return ret;
> +}
> +
> int snapshot_get_encryption_key(struct snapshot_data *data,
> struct uswsusp_key_blob __user *key)
> {
> - u8 aead_key[SNAPSHOT_ENCRYPTION_KEY_SIZE];
> u8 nonce[USWSUSP_KEY_NONCE_SIZE];
> + struct trusted_key_payload *payload;
> int rc;
> /* Don't pull a random key from a world that can be reset. */
> if (data->ready)
> @@ -399,21 +487,28 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
> get_random_bytes(nonce, sizeof(nonce));
> memcpy(&data->nonce_low, &nonce[0], sizeof(data->nonce_low));
> memcpy(&data->nonce_high, &nonce[8], sizeof(data->nonce_high));
> - /* Build a random key */
> - get_random_bytes(aead_key, sizeof(aead_key));
> - rc = crypto_aead_setkey(data->aead_tfm, aead_key, sizeof(aead_key));
> +
> + /* Create a kernel key, and set it. */
> + rc = snapshot_create_kernel_key(data);
> + if (rc)
> + goto fail;
> +
> + payload = data->key->payload.data[0];
> + /* Install the key */
> + rc = crypto_aead_setkey(data->aead_tfm, payload->key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
> if (rc)
> goto fail;
>
> - /* Hand the key back to user mode (to be changed!) */
> - rc = put_user(sizeof(struct uswsusp_key_blob), &key->blob_len);
> + /* Hand the key back to user mode in sealed form. */
> + rc = put_user(payload->blob_len, &key->blob_len);
> if (rc)
> goto fail;
>
> - rc = copy_to_user(&key->blob, &aead_key, sizeof(aead_key));
> + rc = copy_to_user(&key->blob, &payload->blob, payload->blob_len);
> if (rc)
> goto fail;
>
> + /* The nonce just gets handed back in the clear. */
> rc = copy_to_user(&key->nonce, &nonce, sizeof(nonce));
> if (rc)
> goto fail;
> @@ -425,10 +520,99 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
> return rc;
> }
>
> +static int snapshot_load_kernel_key(struct snapshot_data *data,
> + struct uswsusp_key_blob *blob)
> +{
> +
> + const struct cred *cred = current_cred();
> + char *keytemplate = "load\t%s\tkeyhandle=0x81000000";
> + struct tpm_digest *digests = NULL;
> + char *blobstring = NULL;
> + char *keyinfo = NULL;
> + struct tpm_chip *chip;
> + struct key *key = NULL;
> + int i, ret;
> +
> + chip = tpm_default_chip();
> + if (!chip)
> + return -ENODEV;
> +
> + if (!(tpm_is_tpm2(chip))) {
> + ret = -ENODEV;
> + goto out_dev;
> + }
> +
> + ret = tpm_pcr_reset(chip, 23);
> + if (ret)
> + goto out;
> +
> + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
> + GFP_KERNEL);
> + if (!digests)
> + goto out;
> +
> + for (i = 0; i <= chip->nr_allocated_banks; i++) {
> + digests[i].alg_id = chip->allocated_banks[i].alg_id;
> + if (digests[i].alg_id == known_digest.alg_id)
> + memcpy(&digests[i], &known_digest, sizeof(known_digest));
> + }

So I can confirm my unerstanding here: this is just putting a starting
value in the PCR? (As mentioned in the commit log?)

> +
> + ret = tpm_pcr_extend(chip, 23, digests);
> + if (ret != 0)
> + goto out;
> +
> + blobstring = kmalloc(blob->blob_len * 2, GFP_KERNEL);
> + if (!blobstring) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + bin2hex(blobstring, blob->blob, blob->blob_len);
> + keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring);
> + if (!keyinfo) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
> + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
> + NULL);
> +
> + if (IS_ERR(key)) {
> + ret = PTR_ERR(key);
> + key = NULL;
> + goto out;
> + }
> +
> + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
> + NULL);

You want to keep the trailing NUL byte here so it's easier to read back
later? Reading it back will need to verify the trailing NUL regardless.
(Does this get read back?)

> + if (ret != 0)
> + goto out;
> +
> + data->key = key;
> + key = NULL;
> +
> +out:
> + if (key) {
> + key_revoke(key);
> + key_put(key);
> + }
> +
> + kfree(keyinfo);
> + kfree(blobstring);
> + kfree(digests);
> + tpm_pcr_reset(chip, 23);
> +
> +out_dev:
> + put_device(&chip->dev);
> + return ret;
> +}
> +
> int snapshot_set_encryption_key(struct snapshot_data *data,
> struct uswsusp_key_blob __user *key)
> {
> struct uswsusp_key_blob blob;
> + struct trusted_key_payload *payload;
> int rc;
>
> /* It's too late if data's been pushed in. */
> @@ -444,13 +628,18 @@ int snapshot_set_encryption_key(struct snapshot_data *data,
> if (rc)
> goto crypto_setup_fail;
>
> - if (blob.blob_len != sizeof(struct uswsusp_key_blob)) {
> + if (blob.blob_len > sizeof(key->blob)) {
> rc = -EINVAL;
> goto crypto_setup_fail;
> }
>
> + rc = snapshot_load_kernel_key(data, &blob);
> + if (rc)
> + goto crypto_setup_fail;
> +
> + payload = data->key->payload.data[0];
> rc = crypto_aead_setkey(data->aead_tfm,
> - blob.blob,
> + payload->key,
> SNAPSHOT_ENCRYPTION_KEY_SIZE);
>
> if (rc)
> diff --git a/kernel/power/user.h b/kernel/power/user.h
> index 6823e2eba7ec53..591b30bb213349 100644
> --- a/kernel/power/user.h
> +++ b/kernel/power/user.h
> @@ -31,6 +31,7 @@ struct snapshot_data {
> uint64_t crypt_total;
> uint64_t nonce_low;
> uint64_t nonce_high;
> + struct key *key;
> #endif
>
> };
> --
> 2.31.0
>

--
Kees Cook

2022-09-20 23:46:06

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 10/10] PM: hibernate: seal the encryption key with a PCR policy

On Tue, Aug 23, 2022 at 03:25:26PM -0700, Evan Green wrote:
> The key blob is not secret, and by default the TPM will happily unseal
> it regardless of system state. We can protect against that by sealing
> the secret with a PCR policy - if the current PCR state doesn't match,
> the TPM will refuse to release the secret. For now let's just seal it to
> PCR 23. In the long term we may want a more flexible policy around this,
> such as including PCR 7 for PCs or 0 for Chrome OS.
>
> Sourced-from: Matthew Garrett <[email protected]>

If it's a total rewrite, I'd say use:

Suggested-by: Matthew Garrett <...>
Link: https://lore.kernel.org/of/what/it/was/based/on

If it's built on an existing patch, I'd say use:

Co-developed-by: Matthew Garrett <...>
Signed-off-by: Matthew Garrett <...>

But I defer to what Matthew thinks. :)

Also, if you don't hear from Matthew, maybe ping his [email protected]
address.

-Kees

--
Kees Cook

2022-09-21 16:40:41

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Mon, Sep 19, 2022 at 9:51 PM Jarkko Sakkinen <[email protected]> wrote:
>
> On Tue, Sep 13, 2022 at 08:26:09AM -0400, Stefan Berger wrote:
> >
> >
> > On 8/23/22 18:25, Evan Green wrote:
> > > From: Matthew Garrett <[email protected]>
> > >
> > > Under certain circumstances it might be desirable to enable the creation
> > > of TPM-backed secrets that are only accessible to the kernel. In an
> > > ideal world this could be achieved by using TPM localities, but these
> > > don't appear to be available on consumer systems. An alternative is to
> > > simply block userland from modifying one of the resettable PCRs, leaving
> > > it available to the kernel. If the kernel ensures that no userland can
> > > access the TPM while it is carrying out work, it can reset PCR 23,
> > > extend it to an arbitrary value, create or load a secret, and then reset
> > > the PCR again. Even if userland somehow obtains the sealed material, it
> > > will be unable to unseal it since PCR 23 will never be in the
> > > appropriate state.
> > >
> > > From: Matthew Garrett <[email protected]>
> > > Signed-off-by: Matthew Garrett <[email protected]>
> > >
> > > Signed-off-by: Evan Green <[email protected]>
> > > ---
> > > Matthew's original version of this patch is at:
> > > https://patchwork.kernel.org/patch/12096491/
> > >
> > > Changes in v2:
> > > - Fixed sparse warnings
> > >
> > > drivers/char/tpm/Kconfig | 10 +++++++++
> > > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > > drivers/char/tpm/tpm2-space.c | 2 +-
> > > 6 files changed, 97 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > > index 927088b2c3d3f2..4483b61a428b11 100644
> > > --- a/drivers/char/tpm/Kconfig
> > > +++ b/drivers/char/tpm/Kconfig
> > > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > > This driver proxies for firmware TPM running in TEE.
> > > source "drivers/char/tpm/st33zp24/Kconfig"
> > > +
> > > +config TCG_TPM_RESTRICT_PCR
> > > + bool "Restrict userland access to PCR 23"
> > > + depends on TCG_TPM
> > > + help
> > > + If set, block userland from extending or resetting PCR 23. This
> > > + allows it to be restricted to in-kernel use, preventing userland
> > > + from being able to make use of data sealed to the TPM by the kernel.
> > > + This is required for secure hibernation support, but should be left
> > > + disabled if any userland may require access to PCR23.
> > > endif # TCG_TPM
> > > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > > index dc4c0a0a512903..7a4e618c7d1942 100644
> > > --- a/drivers/char/tpm/tpm-dev-common.c
> > > +++ b/drivers/char/tpm/tpm-dev-common.c
> > > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > > priv->response_read = false;
> > > *off = 0;
> > > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > + else
> > > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > +
> > > + if (ret)
> > > + goto out;
> > > +
> > > /*
> > > * If in nonblocking mode schedule an async job to send
> > > * the command return the size.
> > > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > > index a80b341d38eb8c..077c3ca0a127ba 100644
> > > --- a/drivers/char/tpm/tpm.h
> > > +++ b/drivers/char/tpm/tpm.h
> > > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > > int tpm2_probe(struct tpm_chip *chip);
> > > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > > + const void *buf, size_t bufsiz);
> > > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > > int tpm_dev_common_init(void);
> > > void tpm_dev_common_exit(void);
> > > +
> > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > +#define TPM_RESTRICTED_PCR 23
> > > +
> > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > +#else
> > > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > + size_t size)
> > > +{
> > > + return 0;
> > > +}
> > > +
> > > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > + size_t size)
> > > +{
> > > + return 0;
> > > +}
> > > +#endif
> > > #endif
> > > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > > index 8ec743dec26544..318e75ae42fb85 100644
> > > --- a/drivers/char/tpm/tpm1-cmd.c
> > > +++ b/drivers/char/tpm/tpm1-cmd.c
> > > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> > > return 0;
> > > }
> > > +
> > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > > +{
> > > + struct tpm_header *header = (struct tpm_header *)buffer;
> > > + char len, offset;
> > > + __be32 *pcr;
> > > + int pos;
> > > +
> > > + switch (be32_to_cpu(header->ordinal)) {
> > > + case TPM_ORD_PCR_EXTEND:
> > > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > > + return -EINVAL;
> > > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > > + return -EPERM;
> >
> > FYI: TPM 1.2 has transport sessions where the command is tunneled in an
> > encrypted channel and this check could be circumvented...
>
> BTW, Why do we want to support TPM 1.2 at all.
>
> I would not support it for new features. This could be just TPM2 only
> feeature.

I didn't know about the TPM1.2 tunnelling thing, thanks Stefan. Yes,
maybe in light of that and Jarkko's comment we shouldn't bend over
backwards to make this work on TPM1 and just make it a TPM2-only
feature.

Downstream of this decision, in the other patch, "Add support for
in-kernel resetting of PCRs", my instinct is to keep the addition of
tpm1_pcr_reset() just so the newly introduced generic tpm_pcr_reset()
is fully implemented. Let me know if instead I should also drop the
tpm1 side of that as well, in the name of "don't add stuff you're not
using".
-Evan

2022-09-21 18:07:39

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Wed, Sep 21, 2022 at 08:35:35AM -0700, Evan Green wrote:
> On Mon, Sep 19, 2022 at 9:51 PM Jarkko Sakkinen <[email protected]> wrote:
> >
> > On Tue, Sep 13, 2022 at 08:26:09AM -0400, Stefan Berger wrote:
> > >
> > >
> > > On 8/23/22 18:25, Evan Green wrote:
> > > > From: Matthew Garrett <[email protected]>
> > > >
> > > > Under certain circumstances it might be desirable to enable the creation
> > > > of TPM-backed secrets that are only accessible to the kernel. In an
> > > > ideal world this could be achieved by using TPM localities, but these
> > > > don't appear to be available on consumer systems. An alternative is to
> > > > simply block userland from modifying one of the resettable PCRs, leaving
> > > > it available to the kernel. If the kernel ensures that no userland can
> > > > access the TPM while it is carrying out work, it can reset PCR 23,
> > > > extend it to an arbitrary value, create or load a secret, and then reset
> > > > the PCR again. Even if userland somehow obtains the sealed material, it
> > > > will be unable to unseal it since PCR 23 will never be in the
> > > > appropriate state.
> > > >
> > > > From: Matthew Garrett <[email protected]>
> > > > Signed-off-by: Matthew Garrett <[email protected]>
> > > >
> > > > Signed-off-by: Evan Green <[email protected]>
> > > > ---
> > > > Matthew's original version of this patch is at:
> > > > https://patchwork.kernel.org/patch/12096491/
> > > >
> > > > Changes in v2:
> > > > - Fixed sparse warnings
> > > >
> > > > drivers/char/tpm/Kconfig | 10 +++++++++
> > > > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > > > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > > > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > > > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > > > drivers/char/tpm/tpm2-space.c | 2 +-
> > > > 6 files changed, 97 insertions(+), 1 deletion(-)
> > > >
> > > > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > > > index 927088b2c3d3f2..4483b61a428b11 100644
> > > > --- a/drivers/char/tpm/Kconfig
> > > > +++ b/drivers/char/tpm/Kconfig
> > > > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > > > This driver proxies for firmware TPM running in TEE.
> > > > source "drivers/char/tpm/st33zp24/Kconfig"
> > > > +
> > > > +config TCG_TPM_RESTRICT_PCR
> > > > + bool "Restrict userland access to PCR 23"
> > > > + depends on TCG_TPM
> > > > + help
> > > > + If set, block userland from extending or resetting PCR 23. This
> > > > + allows it to be restricted to in-kernel use, preventing userland
> > > > + from being able to make use of data sealed to the TPM by the kernel.
> > > > + This is required for secure hibernation support, but should be left
> > > > + disabled if any userland may require access to PCR23.
> > > > endif # TCG_TPM
> > > > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > > > index dc4c0a0a512903..7a4e618c7d1942 100644
> > > > --- a/drivers/char/tpm/tpm-dev-common.c
> > > > +++ b/drivers/char/tpm/tpm-dev-common.c
> > > > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > > > priv->response_read = false;
> > > > *off = 0;
> > > > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > > > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > + else
> > > > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > +
> > > > + if (ret)
> > > > + goto out;
> > > > +
> > > > /*
> > > > * If in nonblocking mode schedule an async job to send
> > > > * the command return the size.
> > > > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > > > index a80b341d38eb8c..077c3ca0a127ba 100644
> > > > --- a/drivers/char/tpm/tpm.h
> > > > +++ b/drivers/char/tpm/tpm.h
> > > > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > > > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > > > int tpm2_probe(struct tpm_chip *chip);
> > > > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > > > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > > > + const void *buf, size_t bufsiz);
> > > > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > > > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > > > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > > > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > > > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > > > int tpm_dev_common_init(void);
> > > > void tpm_dev_common_exit(void);
> > > > +
> > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > +#define TPM_RESTRICTED_PCR 23
> > > > +
> > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > +#else
> > > > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > + size_t size)
> > > > +{
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > + size_t size)
> > > > +{
> > > > + return 0;
> > > > +}
> > > > +#endif
> > > > #endif
> > > > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > > > index 8ec743dec26544..318e75ae42fb85 100644
> > > > --- a/drivers/char/tpm/tpm1-cmd.c
> > > > +++ b/drivers/char/tpm/tpm1-cmd.c
> > > > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> > > > return 0;
> > > > }
> > > > +
> > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > > > +{
> > > > + struct tpm_header *header = (struct tpm_header *)buffer;
> > > > + char len, offset;
> > > > + __be32 *pcr;
> > > > + int pos;
> > > > +
> > > > + switch (be32_to_cpu(header->ordinal)) {
> > > > + case TPM_ORD_PCR_EXTEND:
> > > > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > > > + return -EINVAL;
> > > > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > > > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > > > + return -EPERM;
> > >
> > > FYI: TPM 1.2 has transport sessions where the command is tunneled in an
> > > encrypted channel and this check could be circumvented...
> >
> > BTW, Why do we want to support TPM 1.2 at all.
> >
> > I would not support it for new features. This could be just TPM2 only
> > feeature.
>
> I didn't know about the TPM1.2 tunnelling thing, thanks Stefan. Yes,
> maybe in light of that and Jarkko's comment we shouldn't bend over
> backwards to make this work on TPM1 and just make it a TPM2-only
> feature.
>
> Downstream of this decision, in the other patch, "Add support for
> in-kernel resetting of PCRs", my instinct is to keep the addition of
> tpm1_pcr_reset() just so the newly introduced generic tpm_pcr_reset()
> is fully implemented. Let me know if instead I should also drop the
> tpm1 side of that as well, in the name of "don't add stuff you're not
> using".
> -Evan

You should drop TPM 1.2 support.

General policy with TPM 1.2:

1. Support legacy.
2. Do no extend the functionality.

BR, Jarkko

2022-09-21 18:27:11

by Jason Gunthorpe

[permalink] [raw]
Subject: Re: [PATCH v2 00/10] Encrypted Hibernation

On Tue, Sep 20, 2022 at 10:46:48AM +0200, Pavel Machek wrote:
> Hi!
>
> > We are exploring enabling hibernation in some new scenarios. However,
> > our security team has a few requirements, listed below:
> > 1. The hibernate image must be encrypted with protection derived from
> > both the platform (eg TPM) and user authentication data (eg
> > password).
> > 2. Hibernation must not be a vector by which a malicious userspace can
> > escalate to the kernel.
>
> Why is #2 reasonable requirement?

These days with kernel lockdown we don't allow userspace to enter the
kernel

> We normally allow userspace with appropriate permissions to update the
> kernel, for example.

And in a lockdown secure boot environment only a signed kernel can be
booted in the first place.

A series like this is effectively carrying the secure boot trust
across the hibernation

Jason

2022-09-21 18:32:06

by Jarkko Sakkinen

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Wed, Sep 21, 2022 at 09:02:29PM +0300, Jarkko Sakkinen wrote:
> On Wed, Sep 21, 2022 at 08:35:35AM -0700, Evan Green wrote:
> > On Mon, Sep 19, 2022 at 9:51 PM Jarkko Sakkinen <[email protected]> wrote:
> > >
> > > On Tue, Sep 13, 2022 at 08:26:09AM -0400, Stefan Berger wrote:
> > > >
> > > >
> > > > On 8/23/22 18:25, Evan Green wrote:
> > > > > From: Matthew Garrett <[email protected]>
> > > > >
> > > > > Under certain circumstances it might be desirable to enable the creation
> > > > > of TPM-backed secrets that are only accessible to the kernel. In an
> > > > > ideal world this could be achieved by using TPM localities, but these
> > > > > don't appear to be available on consumer systems. An alternative is to
> > > > > simply block userland from modifying one of the resettable PCRs, leaving
> > > > > it available to the kernel. If the kernel ensures that no userland can
> > > > > access the TPM while it is carrying out work, it can reset PCR 23,
> > > > > extend it to an arbitrary value, create or load a secret, and then reset
> > > > > the PCR again. Even if userland somehow obtains the sealed material, it
> > > > > will be unable to unseal it since PCR 23 will never be in the
> > > > > appropriate state.
> > > > >
> > > > > From: Matthew Garrett <[email protected]>
> > > > > Signed-off-by: Matthew Garrett <[email protected]>
> > > > >
> > > > > Signed-off-by: Evan Green <[email protected]>
> > > > > ---
> > > > > Matthew's original version of this patch is at:
> > > > > https://patchwork.kernel.org/patch/12096491/
> > > > >
> > > > > Changes in v2:
> > > > > - Fixed sparse warnings
> > > > >
> > > > > drivers/char/tpm/Kconfig | 10 +++++++++
> > > > > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > > > > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > > > > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > > > > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > > > > drivers/char/tpm/tpm2-space.c | 2 +-
> > > > > 6 files changed, 97 insertions(+), 1 deletion(-)
> > > > >
> > > > > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > > > > index 927088b2c3d3f2..4483b61a428b11 100644
> > > > > --- a/drivers/char/tpm/Kconfig
> > > > > +++ b/drivers/char/tpm/Kconfig
> > > > > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > > > > This driver proxies for firmware TPM running in TEE.
> > > > > source "drivers/char/tpm/st33zp24/Kconfig"
> > > > > +
> > > > > +config TCG_TPM_RESTRICT_PCR
> > > > > + bool "Restrict userland access to PCR 23"
> > > > > + depends on TCG_TPM
> > > > > + help
> > > > > + If set, block userland from extending or resetting PCR 23. This
> > > > > + allows it to be restricted to in-kernel use, preventing userland
> > > > > + from being able to make use of data sealed to the TPM by the kernel.
> > > > > + This is required for secure hibernation support, but should be left
> > > > > + disabled if any userland may require access to PCR23.
> > > > > endif # TCG_TPM
> > > > > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > > > > index dc4c0a0a512903..7a4e618c7d1942 100644
> > > > > --- a/drivers/char/tpm/tpm-dev-common.c
> > > > > +++ b/drivers/char/tpm/tpm-dev-common.c
> > > > > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > > > > priv->response_read = false;
> > > > > *off = 0;
> > > > > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > > > > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > > + else
> > > > > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > > +
> > > > > + if (ret)
> > > > > + goto out;
> > > > > +
> > > > > /*
> > > > > * If in nonblocking mode schedule an async job to send
> > > > > * the command return the size.
> > > > > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > > > > index a80b341d38eb8c..077c3ca0a127ba 100644
> > > > > --- a/drivers/char/tpm/tpm.h
> > > > > +++ b/drivers/char/tpm/tpm.h
> > > > > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > > > > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > > > > int tpm2_probe(struct tpm_chip *chip);
> > > > > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > > > > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > > > > + const void *buf, size_t bufsiz);
> > > > > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > > > > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > > > > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > > > > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > > > > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > > > > int tpm_dev_common_init(void);
> > > > > void tpm_dev_common_exit(void);
> > > > > +
> > > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > > +#define TPM_RESTRICTED_PCR 23
> > > > > +
> > > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > > +#else
> > > > > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > > + size_t size)
> > > > > +{
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > > + size_t size)
> > > > > +{
> > > > > + return 0;
> > > > > +}
> > > > > +#endif
> > > > > #endif
> > > > > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > > > > index 8ec743dec26544..318e75ae42fb85 100644
> > > > > --- a/drivers/char/tpm/tpm1-cmd.c
> > > > > +++ b/drivers/char/tpm/tpm1-cmd.c
> > > > > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> > > > > return 0;
> > > > > }
> > > > > +
> > > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > > > > +{
> > > > > + struct tpm_header *header = (struct tpm_header *)buffer;
> > > > > + char len, offset;
> > > > > + __be32 *pcr;
> > > > > + int pos;
> > > > > +
> > > > > + switch (be32_to_cpu(header->ordinal)) {
> > > > > + case TPM_ORD_PCR_EXTEND:
> > > > > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > > > > + return -EINVAL;
> > > > > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > > > > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > > > > + return -EPERM;
> > > >
> > > > FYI: TPM 1.2 has transport sessions where the command is tunneled in an
> > > > encrypted channel and this check could be circumvented...
> > >
> > > BTW, Why do we want to support TPM 1.2 at all.
> > >
> > > I would not support it for new features. This could be just TPM2 only
> > > feeature.
> >
> > I didn't know about the TPM1.2 tunnelling thing, thanks Stefan. Yes,
> > maybe in light of that and Jarkko's comment we shouldn't bend over
> > backwards to make this work on TPM1 and just make it a TPM2-only
> > feature.
> >
> > Downstream of this decision, in the other patch, "Add support for
> > in-kernel resetting of PCRs", my instinct is to keep the addition of
> > tpm1_pcr_reset() just so the newly introduced generic tpm_pcr_reset()
> > is fully implemented. Let me know if instead I should also drop the
> > tpm1 side of that as well, in the name of "don't add stuff you're not
> > using".
> > -Evan
>
> You should drop TPM 1.2 support.
>
> General policy with TPM 1.2:
>
> 1. Support legacy.
> 2. Do no extend the functionality.

Why? Because we do not want to add legacy to something that is dying.

BR, Jarkko

2022-09-21 20:43:52

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 02/10] tpm: Allow PCR 23 to be restricted to kernel-only use

On Wed, Sep 21, 2022 at 11:05 AM Jarkko Sakkinen <[email protected]> wrote:
>
> On Wed, Sep 21, 2022 at 09:02:29PM +0300, Jarkko Sakkinen wrote:
> > On Wed, Sep 21, 2022 at 08:35:35AM -0700, Evan Green wrote:
> > > On Mon, Sep 19, 2022 at 9:51 PM Jarkko Sakkinen <[email protected]> wrote:
> > > >
> > > > On Tue, Sep 13, 2022 at 08:26:09AM -0400, Stefan Berger wrote:
> > > > >
> > > > >
> > > > > On 8/23/22 18:25, Evan Green wrote:
> > > > > > From: Matthew Garrett <[email protected]>
> > > > > >
> > > > > > Under certain circumstances it might be desirable to enable the creation
> > > > > > of TPM-backed secrets that are only accessible to the kernel. In an
> > > > > > ideal world this could be achieved by using TPM localities, but these
> > > > > > don't appear to be available on consumer systems. An alternative is to
> > > > > > simply block userland from modifying one of the resettable PCRs, leaving
> > > > > > it available to the kernel. If the kernel ensures that no userland can
> > > > > > access the TPM while it is carrying out work, it can reset PCR 23,
> > > > > > extend it to an arbitrary value, create or load a secret, and then reset
> > > > > > the PCR again. Even if userland somehow obtains the sealed material, it
> > > > > > will be unable to unseal it since PCR 23 will never be in the
> > > > > > appropriate state.
> > > > > >
> > > > > > From: Matthew Garrett <[email protected]>
> > > > > > Signed-off-by: Matthew Garrett <[email protected]>
> > > > > >
> > > > > > Signed-off-by: Evan Green <[email protected]>
> > > > > > ---
> > > > > > Matthew's original version of this patch is at:
> > > > > > https://patchwork.kernel.org/patch/12096491/
> > > > > >
> > > > > > Changes in v2:
> > > > > > - Fixed sparse warnings
> > > > > >
> > > > > > drivers/char/tpm/Kconfig | 10 +++++++++
> > > > > > drivers/char/tpm/tpm-dev-common.c | 8 +++++++
> > > > > > drivers/char/tpm/tpm.h | 21 +++++++++++++++++++
> > > > > > drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++
> > > > > > drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++
> > > > > > drivers/char/tpm/tpm2-space.c | 2 +-
> > > > > > 6 files changed, 97 insertions(+), 1 deletion(-)
> > > > > >
> > > > > > diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> > > > > > index 927088b2c3d3f2..4483b61a428b11 100644
> > > > > > --- a/drivers/char/tpm/Kconfig
> > > > > > +++ b/drivers/char/tpm/Kconfig
> > > > > > @@ -211,4 +211,14 @@ config TCG_FTPM_TEE
> > > > > > This driver proxies for firmware TPM running in TEE.
> > > > > > source "drivers/char/tpm/st33zp24/Kconfig"
> > > > > > +
> > > > > > +config TCG_TPM_RESTRICT_PCR
> > > > > > + bool "Restrict userland access to PCR 23"
> > > > > > + depends on TCG_TPM
> > > > > > + help
> > > > > > + If set, block userland from extending or resetting PCR 23. This
> > > > > > + allows it to be restricted to in-kernel use, preventing userland
> > > > > > + from being able to make use of data sealed to the TPM by the kernel.
> > > > > > + This is required for secure hibernation support, but should be left
> > > > > > + disabled if any userland may require access to PCR23.
> > > > > > endif # TCG_TPM
> > > > > > diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
> > > > > > index dc4c0a0a512903..7a4e618c7d1942 100644
> > > > > > --- a/drivers/char/tpm/tpm-dev-common.c
> > > > > > +++ b/drivers/char/tpm/tpm-dev-common.c
> > > > > > @@ -198,6 +198,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf,
> > > > > > priv->response_read = false;
> > > > > > *off = 0;
> > > > > > + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2)
> > > > > > + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > > > + else
> > > > > > + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size);
> > > > > > +
> > > > > > + if (ret)
> > > > > > + goto out;
> > > > > > +
> > > > > > /*
> > > > > > * If in nonblocking mode schedule an async job to send
> > > > > > * the command return the size.
> > > > > > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > > > > > index a80b341d38eb8c..077c3ca0a127ba 100644
> > > > > > --- a/drivers/char/tpm/tpm.h
> > > > > > +++ b/drivers/char/tpm/tpm.h
> > > > > > @@ -229,6 +229,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
> > > > > > unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> > > > > > int tpm2_probe(struct tpm_chip *chip);
> > > > > > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
> > > > > > +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space,
> > > > > > + const void *buf, size_t bufsiz);
> > > > > > int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > > > > > int tpm2_init_space(struct tpm_space *space, unsigned int buf_size);
> > > > > > void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> > > > > > @@ -244,4 +246,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip);
> > > > > > void tpm_bios_log_teardown(struct tpm_chip *chip);
> > > > > > int tpm_dev_common_init(void);
> > > > > > void tpm_dev_common_exit(void);
> > > > > > +
> > > > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > > > +#define TPM_RESTRICTED_PCR 23
> > > > > > +
> > > > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > > > +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size);
> > > > > > +#else
> > > > > > +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > > > + size_t size)
> > > > > > +{
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer,
> > > > > > + size_t size)
> > > > > > +{
> > > > > > + return 0;
> > > > > > +}
> > > > > > +#endif
> > > > > > #endif
> > > > > > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > > > > > index 8ec743dec26544..318e75ae42fb85 100644
> > > > > > --- a/drivers/char/tpm/tpm1-cmd.c
> > > > > > +++ b/drivers/char/tpm/tpm1-cmd.c
> > > > > > @@ -845,3 +845,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip)
> > > > > > return 0;
> > > > > > }
> > > > > > +
> > > > > > +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR
> > > > > > +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size)
> > > > > > +{
> > > > > > + struct tpm_header *header = (struct tpm_header *)buffer;
> > > > > > + char len, offset;
> > > > > > + __be32 *pcr;
> > > > > > + int pos;
> > > > > > +
> > > > > > + switch (be32_to_cpu(header->ordinal)) {
> > > > > > + case TPM_ORD_PCR_EXTEND:
> > > > > > + if (size < (TPM_HEADER_SIZE + sizeof(u32)))
> > > > > > + return -EINVAL;
> > > > > > + pcr = (__be32 *)&buffer[TPM_HEADER_SIZE];
> > > > > > + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR)
> > > > > > + return -EPERM;
> > > > >
> > > > > FYI: TPM 1.2 has transport sessions where the command is tunneled in an
> > > > > encrypted channel and this check could be circumvented...
> > > >
> > > > BTW, Why do we want to support TPM 1.2 at all.
> > > >
> > > > I would not support it for new features. This could be just TPM2 only
> > > > feeature.
> > >
> > > I didn't know about the TPM1.2 tunnelling thing, thanks Stefan. Yes,
> > > maybe in light of that and Jarkko's comment we shouldn't bend over
> > > backwards to make this work on TPM1 and just make it a TPM2-only
> > > feature.
> > >
> > > Downstream of this decision, in the other patch, "Add support for
> > > in-kernel resetting of PCRs", my instinct is to keep the addition of
> > > tpm1_pcr_reset() just so the newly introduced generic tpm_pcr_reset()
> > > is fully implemented. Let me know if instead I should also drop the
> > > tpm1 side of that as well, in the name of "don't add stuff you're not
> > > using".
> > > -Evan
> >
> > You should drop TPM 1.2 support.
> >
> > General policy with TPM 1.2:
> >
> > 1. Support legacy.
> > 2. Do no extend the functionality.
>
> Why? Because we do not want to add legacy to something that is dying.

Sounds good, I'll drop the TPM1 stuff from the next spin. Thanks!
-Evan

2022-09-23 22:45:35

by Evan Green

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

On Tue, Sep 20, 2022 at 4:04 PM Kees Cook <[email protected]> wrote:
>
> 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).

Will fix.

>
> >
> > Signed-off-by: Evan Green <[email protected]>
> > ---
> > 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?

Sure. The TPM spec names their structure types (TPM2B_CREATION_DATA,
TPM2B_DIGEST, etc), so I'll add comments with the names of the types
as well as the command they came out of.

>
> > [...]
> > @@ -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"?

Sure, will add checks of len to my hunk and the bit above.

>
> > @@ -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.

It wasn't my favorite either, but I felt an urge to be consistent. So
far I've lft this as-is, holler if I should change it.

>
> > + }
> >
> > 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?

The prototype of asn1_action_t looks like this:

typedef int (*asn1_action_t)(void *context,
size_t hdrlen, /* In case of ANY type */
unsigned char tag, /* In case of ANY type */
const void *value, size_t vlen);

I'm not an ASN.1 expert, but from studying asn1_ber_decoder(), it
looks like the core unit of an ASN.1 thing is a TLV, and hdrlen
represents the number of bytes in the datastream that make up the {TL}
portion. Based on the ANY comment above this seems to maybe be useful
for certain generic/undefined object types, but I think it's not
relevant to the types we're using. The vlen arg is the L value of the
TLV, which is what we save away in tpm2_key_context. In
asn1_ber_decoder(), I do see checks that what they pass for vlen stays
within the bounds of the source data buffer (since I knew you'd ask
:)).


>
> > +
> > +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!

Thank you for reviewing it!
-Evan

2022-09-23 23:12:11

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 05/10] security: keys: trusted: Verify creation data

On Tue, Sep 20, 2022 at 4:07 PM Kees Cook <[email protected]> wrote:
>
> On Tue, Aug 23, 2022 at 03:25:21PM -0700, Evan Green wrote:
> > If a loaded key contains creation data, ask the TPM to verify that
> > creation data. This allows users like encrypted hibernate to know that
> > the loaded and parsed creation data has not been tampered with.
> >
> > Partially-sourced-from: Matthew Garrett <[email protected]>
> > Signed-off-by: Evan Green <[email protected]>
> >
> > ---
> > Source material for this change is at:
> > https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/
> >
> > Changes in v2:
> > - Adjust hash len by 2 due to new ASN.1 storage, and add underflow
> > check.
> >
> > include/linux/tpm.h | 1 +
> > security/keys/trusted-keys/trusted_tpm2.c | 77 ++++++++++++++++++++++-
> > 2 files changed, 77 insertions(+), 1 deletion(-)
> >
> > diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> > index 8320cbac6f4009..438f8bc0a50582 100644
> > --- a/include/linux/tpm.h
> > +++ b/include/linux/tpm.h
> > @@ -224,6 +224,7 @@ enum tpm2_command_codes {
> > TPM2_CC_SELF_TEST = 0x0143,
> > TPM2_CC_STARTUP = 0x0144,
> > TPM2_CC_SHUTDOWN = 0x0145,
> > + TPM2_CC_CERTIFYCREATION = 0x014A,
> > TPM2_CC_NV_READ = 0x014E,
> > TPM2_CC_CREATE = 0x0153,
> > TPM2_CC_LOAD = 0x0157,
> > diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
> > index 1d1470b880ca01..f81c6578c7f783 100644
> > --- a/security/keys/trusted-keys/trusted_tpm2.c
> > +++ b/security/keys/trusted-keys/trusted_tpm2.c
> > @@ -691,6 +691,74 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> > return rc;
> > }
> >
> > +/**
> > + * tpm2_certify_creation() - execute a TPM2_CertifyCreation command
> > + *
> > + * @chip: TPM chip to use
> > + * @payload: the key data in clear and encrypted form
> > + * @blob_handle: the loaded TPM handle of the key
> > + *
> > + * Return: 0 on success
> > + * -EINVAL on tpm error status
> > + * < 0 error from tpm_send or tpm_buf_init
> > + */
> > +static int tpm2_certify_creation(struct tpm_chip *chip,
> > + struct trusted_key_payload *payload,
> > + u32 blob_handle)
> > +{
> > + struct tpm_header *head;
> > + struct tpm_buf buf;
> > + int rc;
> > +
> > + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CERTIFYCREATION);
> > + if (rc)
> > + return rc;
> > +
> > + /* Use TPM_RH_NULL for signHandle */
> > + tpm_buf_append_u32(&buf, 0x40000007);
> > +
> > + /* Object handle */
> > + tpm_buf_append_u32(&buf, blob_handle);
> > +
> > + /* Auth */
> > + tpm_buf_append_u32(&buf, 9);
> > + tpm_buf_append_u32(&buf, TPM2_RS_PW);
> > + tpm_buf_append_u16(&buf, 0);
> > + tpm_buf_append_u8(&buf, 0);
> > + tpm_buf_append_u16(&buf, 0);
> > +
> > + /* Qualifying data */
> > + tpm_buf_append_u16(&buf, 0);
> > +
> > + /* Creation data hash */
> > + if (payload->creation_hash_len < 2) {
> > + rc = -EINVAL;
> > + goto out;
> > + }
> > +
> > + tpm_buf_append_u16(&buf, payload->creation_hash_len - 2);
> > + tpm_buf_append(&buf, payload->creation_hash + 2,
> > + payload->creation_hash_len - 2);
> > +
> > + /* signature scheme */
> > + tpm_buf_append_u16(&buf, TPM_ALG_NULL);
> > +
> > + /* creation ticket */
> > + tpm_buf_append(&buf, payload->tk, payload->tk_len);
> > +
> > + rc = tpm_transmit_cmd(chip, &buf, 6, "certifying creation data");
> > + if (rc)
> > + goto out;
> > +
> > + head = (struct tpm_header *)buf.data;
> > +
> > + if (head->return_code != 0)
> > + rc = -EINVAL;
>
> Do you have a reference to this TPM command spec? I have a dim memory of
> some of these commands having success/failure listed separately from
> other things in the reply. Is that true here? (i.e. is the return_code
> only about "yes I replied" and there is a missing "but the answer is no"
> check?)

Here's the link to the volumes:
https://trustedcomputinggroup.org/resource/tpm-library-specification/

The description for TPM2_CertifyCreation in part 3 says this:
```
This command is used to prove the association between an object and
its creation data. The TPM will
validate that the ticket was produced by the TPM and that the ticket
validates the association between a
loaded public area and the provided hash of the creation data (creationHash).
NOTE 1 See 18.1 for description of how the signing scheme is selected.
The TPM will create a test ticket using the Name associated with
objectHandle and creationHash as:
HMAC(proof, (TPM_ST_CREATION || objectHandle→Name || creationHash)) (4)
This ticket is then compared to creation ticket. If the tickets are
not the same, the TPM shall return
TPM_RC_TICKET.
If the ticket is valid, then the TPM will create a TPMS_ATTEST
structure and place creationHash of the
command in the creationHash field of the structure. The Name
associated with objectHandle will be
included in the attestation data that is then signed using the key
associated with signHandle.
```

So my aim there was to check responseCode in the response, and as long
as it's success and not RC_TICKET I should be ok. Though I see now
from other examples I should have used
(be32_to_cpu(header->return_code) != TPM2_RC_SUCCESS). I will change
that.


>
> > +out:
> > + tpm_buf_destroy(&buf);
> > + return rc;
> > +}
> > +
> > /**
> > * tpm2_unseal_trusted() - unseal the payload of a trusted key
> > *
> > @@ -716,8 +784,15 @@ int tpm2_unseal_trusted(struct tpm_chip *chip,
> > goto out;
> >
> > rc = tpm2_unseal_cmd(chip, payload, options, blob_handle);
> > - tpm2_flush_context(chip, blob_handle);
> > + if (rc)
> > + goto flush;
> > +
> > + if (payload->creation_len)
> > + rc = tpm2_certify_creation(chip, payload, blob_handle);
> >
> > +
> > +flush:
> > + tpm2_flush_context(chip, blob_handle);
> > out:
> > tpm_put_ops(chip);
> >
> > --
> > 2.31.0
> >
>
> Otherwise looks good to me. :)
>
> --
> Kees Cook

2022-09-23 23:21:08

by Evan Green

[permalink] [raw]
Subject: Re: [PATCH v2 07/10] PM: hibernate: Use TPM-backed keys to encrypt image

On Tue, Sep 20, 2022 at 4:16 PM Kees Cook <[email protected]> wrote:
>
> On Tue, Aug 23, 2022 at 03:25:23PM -0700, Evan Green wrote:
> > When using encrypted hibernate images, have the TPM create a key for us
> > and seal it. By handing back a sealed blob instead of the raw key, we
> > prevent usermode from being able to decrypt and tamper with the
> > hibernate image on a different machine.
> >
> > We'll also go through the motions of having PCR23 set to a known value at
> > the time of key creation and unsealing. Currently there's nothing that
> > enforces the contents of PCR23 as a condition to unseal the key blob,
> > that will come in a later change.
> >
> > Sourced-from: Matthew Garrett <[email protected]>
> > Signed-off-by: Evan Green <[email protected]>
> >
> > ---
> > Matthew's incarnation of this patch is at:
> > https://patchwork.kernel.org/project/linux-pm/patch/[email protected]/
> >
> > Changes in v2:
> > - Rework load/create_kernel_key() to eliminate a label (Andrey)
> > - Call put_device() needed from calling tpm_default_chip().
> >
> > kernel/power/Kconfig | 2 +
> > kernel/power/snapenc.c | 207 +++++++++++++++++++++++++++++++++++++++--
> > kernel/power/user.h | 1 +
> > 3 files changed, 201 insertions(+), 9 deletions(-)
> >
> > diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> > index cd574af0b43379..ff15361c5d8fad 100644
> > --- a/kernel/power/Kconfig
> > +++ b/kernel/power/Kconfig
> > @@ -96,6 +96,8 @@ config ENCRYPTED_HIBERNATION
> > bool "Encryption support for userspace snapshots"
> > depends on HIBERNATION_SNAPSHOT_DEV
> > depends on CRYPTO_AEAD2=y
> > + depends on KEYS
> > + depends on TRUSTED_KEYS
>
> nit: TRUSTED_KEYS already depends on KEYS, so the KEYS depend can be
> dropped.
>
> > default n
> > help
> > Enable support for kernel-based encryption of hibernation snapshots
> > diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
> > index cb90692d6ab83a..90079f6d4f184b 100644
> > --- a/kernel/power/snapenc.c
> > +++ b/kernel/power/snapenc.c
> > @@ -4,13 +4,23 @@
> > #include <linux/crypto.h>
> > #include <crypto/aead.h>
> > #include <crypto/gcm.h>
> > +#include <keys/trusted-type.h>
> > +#include <linux/key-type.h>
> > #include <linux/random.h>
> > #include <linux/mm.h>
> > +#include <linux/tpm.h>
> > #include <linux/uaccess.h>
> >
> > #include "power.h"
> > #include "user.h"
> >
> > +/* sha256("To sleep, perchance to dream") */
> > +static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
> > + .digest = {0x92, 0x78, 0x3d, 0x79, 0x2d, 0x00, 0x31, 0xb0, 0x55, 0xf9,
> > + 0x1e, 0x0d, 0xce, 0x83, 0xde, 0x1d, 0xc4, 0xc5, 0x8e, 0x8c,
> > + 0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
> > + 0x5f, 0x49}};
> > +
> > /* Encrypt more data from the snapshot into the staging area. */
> > static int snapshot_encrypt_refill(struct snapshot_data *data)
> > {
> > @@ -313,6 +323,12 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
> > {
> > int i;
> >
> > + if (data->key) {
> > + key_revoke(data->key);
> > + key_put(data->key);
> > + data->key = NULL;
> > + }
> > +
> > if (data->aead_req) {
> > aead_request_free(data->aead_req);
> > data->aead_req = NULL;
> > @@ -381,11 +397,83 @@ static int snapshot_setup_encryption_common(struct snapshot_data *data)
> > return rc;
> > }
> >
> > +static int snapshot_create_kernel_key(struct snapshot_data *data)
> > +{
> > + const struct cred *cred = current_cred();
> > + struct tpm_digest *digests = NULL;
> > + struct tpm_chip *chip;
> > + struct key *key = NULL;
> > + int ret, i;
> > + /* Create a key sealed by the SRK. */
> > + char *keyinfo = "new\t32\tkeyhandle=0x81000000";
> > +
> > + chip = tpm_default_chip();
> > + if (!chip)
> > + return -ENODEV;
> > +
> > + if (!(tpm_is_tpm2(chip))) {
> > + ret = -ENODEV;
> > + goto out_dev;
> > + }
> > +
> > + ret = tpm_pcr_reset(chip, 23);
> > + if (ret)
> > + goto out;
> > +
> > + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
> > + GFP_KERNEL);
> > + if (!digests) {
> > + ret = -ENOMEM;
> > + goto out;
> > + }
> > +
> > + for (i = 0; i <= chip->nr_allocated_banks; i++) {
> > + digests[i].alg_id = chip->allocated_banks[i].alg_id;
> > + if (digests[i].alg_id == known_digest.alg_id)
> > + memcpy(&digests[i], &known_digest, sizeof(known_digest));
> > + }
> > +
> > + ret = tpm_pcr_extend(chip, 23, digests);
> > + if (ret != 0)
> > + goto out;
> > +
> > + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
> > + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
> > + NULL);
> > +
> > + if (IS_ERR(key)) {
> > + ret = PTR_ERR(key);
> > + key = NULL;
> > + goto out;
> > + }
> > +
> > + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
> > + NULL);
> > + if (ret != 0)
> > + goto out;
> > +
> > + data->key = key;
> > + key = NULL;
> > +
> > +out:
> > + if (key) {
> > + key_revoke(key);
> > + key_put(key);
> > + }
> > +
> > + kfree(digests);
> > + tpm_pcr_reset(chip, 23);
> > +
> > +out_dev:
> > + put_device(&chip->dev);
> > + return ret;
> > +}
> > +
> > int snapshot_get_encryption_key(struct snapshot_data *data,
> > struct uswsusp_key_blob __user *key)
> > {
> > - u8 aead_key[SNAPSHOT_ENCRYPTION_KEY_SIZE];
> > u8 nonce[USWSUSP_KEY_NONCE_SIZE];
> > + struct trusted_key_payload *payload;
> > int rc;
> > /* Don't pull a random key from a world that can be reset. */
> > if (data->ready)
> > @@ -399,21 +487,28 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
> > get_random_bytes(nonce, sizeof(nonce));
> > memcpy(&data->nonce_low, &nonce[0], sizeof(data->nonce_low));
> > memcpy(&data->nonce_high, &nonce[8], sizeof(data->nonce_high));
> > - /* Build a random key */
> > - get_random_bytes(aead_key, sizeof(aead_key));
> > - rc = crypto_aead_setkey(data->aead_tfm, aead_key, sizeof(aead_key));
> > +
> > + /* Create a kernel key, and set it. */
> > + rc = snapshot_create_kernel_key(data);
> > + if (rc)
> > + goto fail;
> > +
> > + payload = data->key->payload.data[0];
> > + /* Install the key */
> > + rc = crypto_aead_setkey(data->aead_tfm, payload->key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
> > if (rc)
> > goto fail;
> >
> > - /* Hand the key back to user mode (to be changed!) */
> > - rc = put_user(sizeof(struct uswsusp_key_blob), &key->blob_len);
> > + /* Hand the key back to user mode in sealed form. */
> > + rc = put_user(payload->blob_len, &key->blob_len);
> > if (rc)
> > goto fail;
> >
> > - rc = copy_to_user(&key->blob, &aead_key, sizeof(aead_key));
> > + rc = copy_to_user(&key->blob, &payload->blob, payload->blob_len);
> > if (rc)
> > goto fail;
> >
> > + /* The nonce just gets handed back in the clear. */
> > rc = copy_to_user(&key->nonce, &nonce, sizeof(nonce));
> > if (rc)
> > goto fail;
> > @@ -425,10 +520,99 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
> > return rc;
> > }
> >
> > +static int snapshot_load_kernel_key(struct snapshot_data *data,
> > + struct uswsusp_key_blob *blob)
> > +{
> > +
> > + const struct cred *cred = current_cred();
> > + char *keytemplate = "load\t%s\tkeyhandle=0x81000000";
> > + struct tpm_digest *digests = NULL;
> > + char *blobstring = NULL;
> > + char *keyinfo = NULL;
> > + struct tpm_chip *chip;
> > + struct key *key = NULL;
> > + int i, ret;
> > +
> > + chip = tpm_default_chip();
> > + if (!chip)
> > + return -ENODEV;
> > +
> > + if (!(tpm_is_tpm2(chip))) {
> > + ret = -ENODEV;
> > + goto out_dev;
> > + }
> > +
> > + ret = tpm_pcr_reset(chip, 23);
> > + if (ret)
> > + goto out;
> > +
> > + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
> > + GFP_KERNEL);
> > + if (!digests)
> > + goto out;
> > +
> > + for (i = 0; i <= chip->nr_allocated_banks; i++) {
> > + digests[i].alg_id = chip->allocated_banks[i].alg_id;
> > + if (digests[i].alg_id == known_digest.alg_id)
> > + memcpy(&digests[i], &known_digest, sizeof(known_digest));
> > + }
>
> So I can confirm my unerstanding here: this is just putting a starting
> value in the PCR? (As mentioned in the commit log?)

Correct, the PCR is reset above and set (well, extended from 0) to the
magic value here ahead of the unseal operation. Once we add the policy
in patch 10, the TPM will refuse to unseal the key unless PCR23 is set
to this magic value.

>
> > +
> > + ret = tpm_pcr_extend(chip, 23, digests);
> > + if (ret != 0)
> > + goto out;
> > +
> > + blobstring = kmalloc(blob->blob_len * 2, GFP_KERNEL);
> > + if (!blobstring) {
> > + ret = -ENOMEM;
> > + goto out;
> > + }
> > +
> > + bin2hex(blobstring, blob->blob, blob->blob_len);
> > + keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring);
> > + if (!keyinfo) {
> > + ret = -ENOMEM;
> > + goto out;
> > + }
> > +
> > + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
> > + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
> > + NULL);
> > +
> > + if (IS_ERR(key)) {
> > + ret = PTR_ERR(key);
> > + key = NULL;
> > + goto out;
> > + }
> > +
> > + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
> > + NULL);
>
> You want to keep the trailing NUL byte here so it's easier to read back
> later? Reading it back will need to verify the trailing NUL regardless.
> (Does this get read back?)

Are you referring to the trailing nul on keyinfo? The keyinfo string
is only used within this function, as key_instantiate_and_link()
receives it as a parameter string. I can see that
trusted_instantiate() also null-terminates the buffer defensively for
itself, but it still seemed prudent to hand in a terminated string. I
can remove the + 1 if you think it's better.


>
> > + if (ret != 0)
> > + goto out;
> > +
> > + data->key = key;
> > + key = NULL;
> > +
> > +out:
> > + if (key) {
> > + key_revoke(key);
> > + key_put(key);
> > + }
> > +
> > + kfree(keyinfo);
> > + kfree(blobstring);
> > + kfree(digests);
> > + tpm_pcr_reset(chip, 23);
> > +
> > +out_dev:
> > + put_device(&chip->dev);
> > + return ret;
> > +}
> > +
> > int snapshot_set_encryption_key(struct snapshot_data *data,
> > struct uswsusp_key_blob __user *key)
> > {
> > struct uswsusp_key_blob blob;
> > + struct trusted_key_payload *payload;
> > int rc;
> >
> > /* It's too late if data's been pushed in. */
> > @@ -444,13 +628,18 @@ int snapshot_set_encryption_key(struct snapshot_data *data,
> > if (rc)
> > goto crypto_setup_fail;
> >
> > - if (blob.blob_len != sizeof(struct uswsusp_key_blob)) {
> > + if (blob.blob_len > sizeof(key->blob)) {
> > rc = -EINVAL;
> > goto crypto_setup_fail;
> > }
> >
> > + rc = snapshot_load_kernel_key(data, &blob);
> > + if (rc)
> > + goto crypto_setup_fail;
> > +
> > + payload = data->key->payload.data[0];
> > rc = crypto_aead_setkey(data->aead_tfm,
> > - blob.blob,
> > + payload->key,
> > SNAPSHOT_ENCRYPTION_KEY_SIZE);
> >
> > if (rc)
> > diff --git a/kernel/power/user.h b/kernel/power/user.h
> > index 6823e2eba7ec53..591b30bb213349 100644
> > --- a/kernel/power/user.h
> > +++ b/kernel/power/user.h
> > @@ -31,6 +31,7 @@ struct snapshot_data {
> > uint64_t crypt_total;
> > uint64_t nonce_low;
> > uint64_t nonce_high;
> > + struct key *key;
> > #endif
> >
> > };
> > --
> > 2.31.0
> >
>
> --
> Kees Cook

2022-09-24 05:48:11

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v2 07/10] PM: hibernate: Use TPM-backed keys to encrypt image

On Fri, Sep 23, 2022 at 03:23:43PM -0700, Evan Green wrote:
> On Tue, Sep 20, 2022 at 4:16 PM Kees Cook <[email protected]> wrote:
> > On Tue, Aug 23, 2022 at 03:25:23PM -0700, Evan Green wrote:
> > > [...]
> > > + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
> > > + NULL);
> >
> > You want to keep the trailing NUL byte here so it's easier to read back
> > later? Reading it back will need to verify the trailing NUL regardless.
> > (Does this get read back?)
>
> Are you referring to the trailing nul on keyinfo? The keyinfo string
> is only used within this function, as key_instantiate_and_link()
> receives it as a parameter string. I can see that
> trusted_instantiate() also null-terminates the buffer defensively for
> itself, but it still seemed prudent to hand in a terminated string. I
> can remove the + 1 if you think it's better.

No, I like having the trailing NUL byte -- it seems more robust that
way. I just wanted to understand who the consumer was going to be. I'm
clear now; thank you! :)

--
Kees Cook