KEYS: changes for v7.4-rc1

HIGHLIGHTS
 ==========
 
 A patch that Kyle Zeng submitted addresses a critical bug and complements
 the ongoing rhashtable migration. The commit in question is de891c73c1
 ("keys: make keyring indexes independent of kernel addresses").
 
 The index value for the  associative array has been calculated with the
 following formula:
 
 	mult_64x32_and_fold(description + type address + domain address)
 
 By creating a sequence of dummy keys (e.g., "A", "B", "C", ...), it is
 trivial to form a group of equations with a predictable hash function, and
 ultimately break KASLR.
 
 Kyle's solution is based on an ephemeral SipHash key and an atomic 64-bit
 counter, which is encapsulated to a key_index_id(). This function is then
 used to assign identifiers for types and domains, and since the IDs are
 unique for each power on, they are as unique as memory addresses are.
 
 With these ingredients the index value calculation can be changed into
 form, which is not leaking any kernel addresses:
 
 	SipHash(description + type id + domain id)
 
 This is exactly what the Kyle's commit does.
 
 FEATURES AND IMPROVEMENTS
 =========================
 
 - TPM fields were moved from trusted_key_options to the TPM backend.
   They cost memory for non-TPM backends.
 - A TPM trusted key now caches tpm_is_tpm2() at the root of each call
   chain, and passes the result as a parameter. This decreases the amount
   of I/O traffic given that tpm_is_tpm2() starts and stops the chip via
   tpm_try_get_ops() and tpm_put_ops().
 
 BUGS
 ====
 
 - A TPM trusted key corrupted the return value of a failed key unsealing
   operation if the pcrlock feature was used.
 - A TPM 2 trusted key allocated an off-by-four too large buffer for the
   decoded ASN.1 blob coming from keyctl add trusted.
 - Sometimes a TPM 2 trusted key rejected created keys based on the
   TPM2_Create response size, treating them as too large. Now it correctly
   checks the extracted TPM2 key blob size.
 - The keyctl reject operation raced with keyctl restrict_keyring because
   keyring->sem was not held in key_reject_and_link() before accessing
   keyring->restrict_link.
 - keyctl_describe_key() was not holding key->sem while accessing its
   fields, which can lead to use-after-free of a retired key type.
 - find_keyring_by_name() and procfs code were reading the UID from
   key->user->uid instead of key->uid. The latter is the cached value and
   is meant to protect against a use-after-free caused by keyctl chown.
 - search_process_keyrings_rcu() did not properly RCU-dereference the
   authorization key payload, which could cause a null dereference if the
   payload was revoked.
 - keyctl_read_key() checked key->type->read without holding key->sem,
   which can lead to use-after-free if the type is retired. Based on my
   suggestion, this was addressed in __keyctl_read_key(), which already
   did take key->sem.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRE6pSOnaBC00OEHEIaerohdGur0gUCatjKPgAKCRAaerohdGur
 0iVIAP9N2OG7/93a2WbH14jxbEQv2zWEZlV0lz4b4NdV0qyhBwEArta0xov2A57X
 nGFNGEYOjtOS1WFJlvBetu0G9Uz/TAA=
 =Xkuf
 -----END PGP SIGNATURE-----

Merge tag 'keys-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd

Pull keys updates from Jarkko Sakkinen:
 "Highlight:

   - Don't leak local kernel addresses (and complement the ongoing
     rhashtable migration) (Kyle Zeng)

     The index value for the associative array has been calculated with
     the following formula:

	mult_64x32_and_fold(description + type address + domain address)

     By creating a sequence of dummy keys (e.g., "A", "B", "C", ...), it
     is trivial to form a group of equations with a predictable hash
     function, and ultimately break KASLR.

     Kyle's solution is based on an ephemeral SipHash key and an atomic
     64-bit counter, which is encapsulated to a key_index_id(). This
     function is then used to assign identifiers for types and domains,
     and since the IDs are unique for each power on, they are as unique
     as memory addresses are.

     With these ingredients the index value calculation can be changed
     into form, which is not leaking any kernel addresses:

	SipHash(description + type id + domain id)

  Features and improvements:

   - TPM fields were moved from trusted_key_options to the TPM backend.
     They cost memory for non-TPM backends.

   - A TPM trusted key now caches tpm_is_tpm2() at the root of each call
     chain, and passes the result as a parameter. This decreases the
     amount of I/O traffic given that tpm_is_tpm2() starts and stops the
     chip via tpm_try_get_ops() and tpm_put_ops().

  Bugfixes:

   - A TPM trusted key corrupted the return value of a failed key
     unsealing operation if the pcrlock feature was used.

   - A TPM 2 trusted key allocated an off-by-four too large buffer for
     the decoded ASN.1 blob coming from keyctl add trusted.

   - Sometimes a TPM 2 trusted key rejected created keys based on the
     TPM2_Create response size, treating them as too large. Now it
     correctly checks the extracted TPM2 key blob size.

   - The keyctl reject operation raced with keyctl restrict_keyring
     because keyring->sem was not held in key_reject_and_link() before
     accessing keyring->restrict_link.

   - keyctl_describe_key() was not holding key->sem while accessing its
     fields, which can lead to use-after-free of a retired key type.

   - find_keyring_by_name() and procfs code were reading the UID from
     key->user->uid instead of key->uid. The latter is the cached value
     and is meant to protect against a use-after-free caused by keyctl
     chown.

   - search_process_keyrings_rcu() did not properly RCU-dereference the
     authorization key payload, which could cause a null dereference if
     the payload was revoked.

   - keyctl_read_key() checked key->type->read without holding key->sem,
     which can lead to use-after-free if the type is retired. Based on
     my suggestion, this was addressed in __keyctl_read_key(), which
     already did take key->sem"

* tag 'keys-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd:
  keys/trusted_keys: reuse TPM version in option handling
  security: keys: Fix comment of search_process_keyrings_rcu()
  keys/trusted/tpm2: Validate TPM2_Create object sizes separately
  keys/trusted_keys: move TPM-specific fields into struct trusted_key_tpm
  keys: Protect the read method check against type removal
  keys: Avoid owner account dereferences in /proc/keys
  keys: make keyring indexes independent of kernel addresses
  keys: Check the authorization payload in process keyring searches
  keys: Avoid the owner account dereference in named keyring lookup
  keys: Protect the type name while describing a key
  KEYS: Fix key_reject_and_link() race with keyring restriction
  KEYS: trusted: Fix blob allocation size in tpm2_key_decode()
  keys/trusted_keys: return immediately after TPM unseal failure
This commit is contained in:
Linus Torvalds 2026-10-22 10:02:37 -10:00
commit 14785eff40
11 changed files with 258 additions and 212 deletions

View File

@ -39,17 +39,6 @@ struct trusted_key_payload {
struct trusted_key_options {
uint16_t keytype;
uint32_t keyhandle;
unsigned char keyauth[TPM_DIGEST_SIZE];
uint32_t blobauth_len;
unsigned char blobauth[TPM_DIGEST_SIZE];
uint32_t pcrinfo_len;
unsigned char pcrinfo[MAX_PCRINFO_SIZE];
int pcrlock;
uint32_t hash;
uint32_t policydigest_len;
unsigned char policydigest[MAX_DIGEST_SIZE];
uint32_t policyhandle;
void *private;
};

View File

@ -6,6 +6,20 @@
extern struct trusted_key_ops trusted_key_tpm_ops;
struct trusted_key_tpm {
u32 keyhandle;
unsigned char keyauth[TPM_DIGEST_SIZE];
u32 blobauth_len;
unsigned char blobauth[TPM_DIGEST_SIZE];
u32 pcrinfo_len;
unsigned char pcrinfo[MAX_PCRINFO_SIZE];
int pcrlock;
u32 hash;
u32 policydigest_len;
unsigned char policydigest[MAX_DIGEST_SIZE];
u32 policyhandle;
};
int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_payload *payload,
struct trusted_key_options *options);

View File

@ -161,6 +161,7 @@ struct key_type {
const void *in, const void *in2);
/* internal fields */
atomic64_t index_id; /* Opaque keyring index ID */
struct list_head link; /* link in types list */
struct lock_class_key lock_class; /* key->sem lock class */
} __randomize_layout;

View File

@ -107,6 +107,7 @@ struct keyring_name;
struct key_tag {
struct rcu_head rcu;
atomic64_t index_id; /* Opaque keyring index ID */
refcount_t usage;
bool removed; /* T when subject removed */
};
@ -129,6 +130,8 @@ struct keyring_index_key {
struct key_type *type;
struct key_tag *domain_tag; /* Domain of operation */
const char *description;
u64 type_id; /* Cached index IDs */
u64 domain_id;
};
union key_payload {
@ -251,6 +254,8 @@ struct key {
struct key_type *type; /* type of key */
struct key_tag *domain_tag; /* Domain of operation */
char *description;
u64 type_id;
u64 domain_id;
};
};

View File

@ -588,11 +588,18 @@ int key_reject_and_link(struct key *key,
ret = -EBUSY;
if (keyring) {
if (keyring->restrict_link)
return -EPERM;
link_ret = __key_link_lock(keyring, &key->index_key);
if (link_ret == 0) {
/*
* Check the restriction under the keyring semaphore.
* keyring_restrict() installs it while holding the
* semaphore, so testing it beforehand races with a
* concurrent restriction install.
*/
if (keyring->restrict_link) {
__key_link_end(keyring, &key->index_key, edit);
return -EPERM;
}
link_ret = __key_link_begin(keyring, &key->index_key, &edit);
if (link_ret < 0)
__key_link_end(keyring, &key->index_key, edit);

View File

@ -677,12 +677,14 @@ long keyctl_describe_key(key_serial_t keyid,
/* calculate how much information we're going to return */
ret = -ENOMEM;
down_read(&key->sem);
infobuf = kasprintf(GFP_KERNEL,
"%s;%d;%d;%08x;",
key->type->name,
from_kuid_munged(current_user_ns(), key->uid),
from_kgid_munged(current_user_ns(), key->gid),
key->perm);
up_read(&key->sem);
if (!infobuf)
goto error2;
infolen = strlen(infobuf);
@ -806,8 +808,12 @@ static long __keyctl_read_key(struct key *key, char *buffer, size_t buflen)
down_read(&key->sem);
ret = key_validate(key);
if (ret == 0)
ret = key->type->read(key, buffer, buflen);
if (ret == 0) {
if (key->type->read)
ret = key->type->read(key, buffer, buflen);
else
ret = -EOPNOTSUPP;
}
up_read(&key->sem);
return ret;
}
@ -861,11 +867,6 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
/* the key is probably readable - now try to read it */
can_read_key:
if (!key->type->read) {
ret = -EOPNOTSUPP;
goto key_put_out;
}
if (!buffer || !buflen) {
/* Get the key length from the read method */
ret = __keyctl_read_key(key, NULL, 0);

View File

@ -14,6 +14,9 @@
#include <linux/err.h>
#include <linux/user_namespace.h>
#include <linux/nsproxy.h>
#include <linux/once.h>
#include <linux/random.h>
#include <linux/siphash.h>
#include <keys/keyring-type.h>
#include <keys/user-type.h>
#include <linux/assoc_array_priv.h>
@ -147,49 +150,46 @@ static int keyring_instantiate(struct key *keyring,
}
/*
* Multiply 64-bits by 32-bits to 96-bits and fold back to 64-bit. Ideally we'd
* fold the carry back too, but that requires inline asm.
* Give key types and domains address-independent identities. Zero means that
* an ID has not yet been allocated. As with inode sequence numbers used by
* futexes, this relies on the 64-bit counter not wrapping in a machine's
* lifetime.
*/
static u64 mult_64x32_and_fold(u64 x, u32 y)
static u64 key_index_id(atomic64_t *id)
{
u64 hi = (u64)(u32)(x >> 32) * y;
u64 lo = (u64)(u32)(x) * y;
return lo + ((u64)(u32)hi << 32) + (u32)(hi >> 32);
static atomic64_t next_id;
u64 old, new;
old = atomic64_read(id);
if (likely(old))
return old;
do {
new = atomic64_inc_return(&next_id);
} while (WARN_ON_ONCE(!new));
old = 0;
if (!atomic64_try_cmpxchg_relaxed(id, &old, new))
return old;
return new;
}
/*
* Hash a key type and description.
* Hash a key type, domain and description. KEYCTL_READ exposes the tree's
* traversal order, so no part of the index may contain kernel addresses.
*/
static void hash_key_type_and_desc(struct keyring_index_key *index_key)
{
const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP;
const unsigned long fan_mask = ASSOC_ARRAY_FAN_MASK;
const char *description = index_key->description;
unsigned long hash, type;
u32 piece;
static siphash_key_t hash_key __read_mostly;
unsigned long hash;
u64 acc;
int n, desc_len = index_key->desc_len;
type = (unsigned long)index_key->type;
acc = mult_64x32_and_fold(type, desc_len + 13);
acc = mult_64x32_and_fold(acc, 9207);
piece = (unsigned long)index_key->domain_tag;
acc = mult_64x32_and_fold(acc, piece);
acc = mult_64x32_and_fold(acc, 9207);
for (;;) {
n = desc_len;
if (n <= 0)
break;
if (n > 4)
n = 4;
piece = 0;
memcpy(&piece, description, n);
description += n;
desc_len -= n;
acc = mult_64x32_and_fold(acc, piece);
acc = mult_64x32_and_fold(acc, 9207);
}
get_random_once(&hash_key, sizeof(hash_key));
acc = siphash(index_key->description, index_key->desc_len, &hash_key);
acc = siphash_3u64(index_key->type_id, index_key->domain_id, acc,
&hash_key);
/* Fold the hash down to 32 bits if need be. */
hash = acc;
@ -209,7 +209,7 @@ static void hash_key_type_and_desc(struct keyring_index_key *index_key)
/*
* Finalise an index key to include a part of the description actually in the
* index key, to set the domain tag and to calculate the hash.
* index key, to set the domain tag and index IDs, and to calculate the hash.
*/
void key_set_index_key(struct keyring_index_key *index_key)
{
@ -225,6 +225,14 @@ void key_set_index_key(struct keyring_index_key *index_key)
index_key->domain_tag = &default_domain_tag;
}
/* Preserve the IDs when copying an existing key's index. In
* particular, its type may already have been unregistered.
*/
if (!index_key->type_id)
index_key->type_id = key_index_id(&index_key->type->index_id);
if (!index_key->domain_id)
index_key->domain_id = key_index_id(&index_key->domain_tag->index_id);
hash_key_type_and_desc(index_key);
}
@ -263,15 +271,19 @@ void key_remove_domain(struct key_tag *domain_tag)
/*
* Build the next index key chunk.
*
* The hash and inline description are followed by the 64-bit type and domain
* IDs, low word first, and then by the rest of the description.
* We return it one word-sized chunk at a time.
*/
static unsigned long keyring_get_key_chunk(const void *data, int level)
{
const unsigned int id_chunks = sizeof(u64) / sizeof(unsigned long);
const struct keyring_index_key *index_key = data;
int desc_len = index_key->desc_len;
unsigned long chunk = 0;
const u8 *d;
int desc_len = index_key->desc_len, n = sizeof(chunk);
int n = sizeof(chunk);
unsigned int offset;
const u8 *d;
level /= ASSOC_ARRAY_KEY_CHUNK_SIZE;
switch (level) {
@ -279,27 +291,30 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
return index_key->hash;
case 1:
return index_key->x;
case 2:
return (unsigned long)index_key->type;
case 3:
return (unsigned long)index_key->domain_tag;
default:
level -= 4;
offset = sizeof(index_key->desc) + level * sizeof(long);
if (desc_len <= offset)
return 0;
d = index_key->description + offset;
desc_len -= offset;
if (desc_len > n)
desc_len = n;
d += desc_len;
do {
chunk <<= 8;
chunk |= *--d;
} while (--desc_len > 0);
return chunk;
}
level -= 2;
if (level < id_chunks)
return index_key->type_id >> (level * ASSOC_ARRAY_KEY_CHUNK_SIZE);
level -= id_chunks;
if (level < id_chunks)
return index_key->domain_id >> (level * ASSOC_ARRAY_KEY_CHUNK_SIZE);
level -= id_chunks;
offset = sizeof(index_key->desc) + level * sizeof(long);
if (desc_len <= offset)
return 0;
d = index_key->description + offset;
desc_len -= offset;
if (desc_len > n)
desc_len = n;
d += desc_len;
do {
chunk <<= 8;
chunk |= *--d;
} while (--desc_len > 0);
return chunk;
}
static unsigned long keyring_get_object_key_chunk(const void *object, int level)
@ -331,6 +346,7 @@ static int keyring_diff_objects(const void *object, const void *data)
const struct keyring_index_key *b = data;
unsigned long seg_a, seg_b;
int level, i;
u64 diff;
level = 0;
seg_a = a->hash;
@ -339,28 +355,22 @@ static int keyring_diff_objects(const void *object, const void *data)
goto differ;
level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
/* The number of bits contributed by the hash is controlled by a
* constant in the assoc_array headers. Everything else thereafter we
* can deal with as being machine word-size dependent.
*/
/* The inline description contributes one machine-word-sized chunk. */
seg_a = a->x;
seg_b = b->x;
if ((seg_a ^ seg_b) != 0)
goto differ;
level += sizeof(unsigned long);
/* The next bit may not work on big endian */
seg_a = (unsigned long)a->type;
seg_b = (unsigned long)b->type;
if ((seg_a ^ seg_b) != 0)
goto differ;
level += sizeof(unsigned long);
diff = a->type_id ^ b->type_id;
if (diff)
return level * 8 + __ffs64(diff);
level += sizeof(u64);
seg_a = (unsigned long)a->domain_tag;
seg_b = (unsigned long)b->domain_tag;
if ((seg_a ^ seg_b) != 0)
goto differ;
level += sizeof(unsigned long);
diff = a->domain_id ^ b->domain_id;
if (diff)
return level * 8 + __ffs64(diff);
level += sizeof(u64);
i = sizeof(a->desc);
if (a->desc_len <= i)
@ -1158,7 +1168,7 @@ struct key *find_keyring_by_name(const char *name, bool uid_keyring)
* grants Search permission and that hasn't been revoked
*/
list_for_each_entry(keyring, &ns->keyring_name_list, name_link) {
if (!kuid_has_mapping(ns, keyring->user->uid))
if (!kuid_has_mapping(ns, keyring->uid))
continue;
if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))

View File

@ -68,7 +68,7 @@ static struct rb_node *key_serial_next(struct seq_file *p, struct rb_node *n)
n = rb_next(n);
while (n) {
struct key *key = rb_entry(n, struct key, serial_node);
if (kuid_has_mapping(user_ns, key->user->uid))
if (kuid_has_mapping(user_ns, key->uid))
break;
n = rb_next(n);
}
@ -100,7 +100,7 @@ static struct key *find_ge_key(struct seq_file *p, key_serial_t id)
return NULL;
for (;;) {
if (kuid_has_mapping(user_ns, minkey->user->uid))
if (kuid_has_mapping(user_ns, minkey->uid))
return minkey;
n = rb_next(&minkey->serial_node);
if (!n)

View File

@ -528,7 +528,7 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
/*
* Search the process keyrings attached to the supplied cred for the first
* matching key in the manner of search_my_process_keyrings(), but also search
* matching key in the manner of search_cred_keyrings_rcu(), but also search
* the keys attached to the assumed authorisation key using its credentials if
* one is available.
*
@ -556,9 +556,8 @@ key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx)
) {
const struct cred *cred = ctx->cred;
if (key_validate(cred->request_key_auth) == 0) {
rka = ctx->cred->request_key_auth->payload.data[0];
rka = dereference_key_rcu(cred->request_key_auth);
if (rka && key_validate(cred->request_key_auth) == 0) {
//// was search_process_keyrings() [ie. recursive]
ctx->cred = rka->cred;
key_ref = search_cred_keyrings_rcu(ctx);

View File

@ -48,15 +48,17 @@ enum {
#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(struct trusted_key_options *o)
{
struct trusted_key_tpm *private = o->private;
if (!trusted_debug)
return;
pr_debug("sealing key type %d\n", o->keytype);
pr_debug("sealing key handle %0X\n", o->keyhandle);
pr_debug("pcrlock %d\n", o->pcrlock);
pr_debug("pcrinfo %d\n", o->pcrinfo_len);
pr_debug("sealing key handle %0X\n", private->keyhandle);
pr_debug("pcrlock %d\n", private->pcrlock);
pr_debug("pcrinfo %d\n", private->pcrinfo_len);
print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
16, 1, o->pcrinfo, o->pcrinfo_len, 0);
16, 1, private->pcrinfo, private->pcrinfo_len, 0);
}
static inline void dump_sess(struct osapsess *s)
@ -626,6 +628,7 @@ static int tpm_unseal(struct tpm_buf *tb,
static int key_seal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
struct trusted_key_tpm *private = o->private;
int ret;
struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
@ -637,9 +640,10 @@ static int key_seal(struct trusted_key_payload *p,
/* include migratable flag at end of sealed key */
p->key[p->key_len] = p->migratable;
ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth,
ret = tpm_seal(tb, o->keytype, private->keyhandle, private->keyauth,
p->key, p->key_len + 1, p->blob, &p->blob_len,
o->blobauth, o->pcrinfo, o->pcrinfo_len);
private->blobauth, private->pcrinfo,
private->pcrinfo_len);
if (ret < 0)
pr_info("srkseal failed (%d)\n", ret);
@ -652,6 +656,7 @@ static int key_seal(struct trusted_key_payload *p,
static int key_unseal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
struct trusted_key_tpm *private = o->private;
int ret;
struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
@ -660,8 +665,8 @@ static int key_unseal(struct trusted_key_payload *p,
tpm_buf_init(tb, TPM_BUFSIZE);
ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
o->blobauth, p->key, &p->key_len);
ret = tpm_unseal(tb, private->keyhandle, private->keyauth, p->blob,
p->blob_len, private->blobauth, p->key, &p->key_len);
if (ret < 0)
pr_info("srkunseal failed (%d)\n", ret);
else
@ -695,8 +700,9 @@ static const match_table_t key_tokens = {
/* can have zero or more token= options */
static int getoptions(char *c, struct trusted_key_payload *pay,
struct trusted_key_options *opt)
struct trusted_key_options *opt, bool is_tpm2)
{
struct trusted_key_tpm *private = opt->private;
substring_t args[MAX_OPT_ARGS];
char *p = c;
int token;
@ -706,13 +712,8 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
unsigned long token_mask = 0;
unsigned int digest_len;
int i;
int tpm2;
tpm2 = tpm_is_tpm2(chip);
if (tpm2 < 0)
return tpm2;
opt->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
private->hash = is_tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
if (!c)
return 0;
@ -726,11 +727,11 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
switch (token) {
case Opt_pcrinfo:
opt->pcrinfo_len = strlen(args[0].from) / 2;
if (opt->pcrinfo_len > MAX_PCRINFO_SIZE)
private->pcrinfo_len = strlen(args[0].from) / 2;
if (private->pcrinfo_len > MAX_PCRINFO_SIZE)
return -EINVAL;
res = hex2bin(opt->pcrinfo, args[0].from,
opt->pcrinfo_len);
res = hex2bin(private->pcrinfo, args[0].from,
private->pcrinfo_len);
if (res < 0)
return -EINVAL;
break;
@ -739,12 +740,12 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
if (res < 0)
return -EINVAL;
opt->keytype = SEAL_keytype;
opt->keyhandle = handle;
private->keyhandle = handle;
break;
case Opt_keyauth:
if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE)
return -EINVAL;
res = hex2bin(opt->keyauth, args[0].from,
res = hex2bin(private->keyauth, args[0].from,
SHA1_DIGEST_SIZE);
if (res < 0)
return -EINVAL;
@ -755,21 +756,23 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
* hex strings. TPM 2.0 authorizations are simple
* passwords (although it can take a hash as well)
*/
opt->blobauth_len = strlen(args[0].from);
private->blobauth_len = strlen(args[0].from);
if (opt->blobauth_len == 2 * TPM_DIGEST_SIZE) {
res = hex2bin(opt->blobauth, args[0].from,
if (private->blobauth_len == 2 * TPM_DIGEST_SIZE) {
res = hex2bin(private->blobauth, args[0].from,
TPM_DIGEST_SIZE);
if (res < 0)
return -EINVAL;
opt->blobauth_len = TPM_DIGEST_SIZE;
private->blobauth_len = TPM_DIGEST_SIZE;
break;
}
if (tpm2 && opt->blobauth_len <= sizeof(opt->blobauth)) {
memcpy(opt->blobauth, args[0].from,
opt->blobauth_len);
if (is_tpm2 &&
private->blobauth_len <=
sizeof(private->blobauth)) {
memcpy(private->blobauth, args[0].from,
private->blobauth_len);
break;
}
@ -787,41 +790,42 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
res = kstrtoul(args[0].from, 10, &lock);
if (res < 0)
return -EINVAL;
opt->pcrlock = lock;
private->pcrlock = lock;
break;
case Opt_hash:
if (test_bit(Opt_policydigest, &token_mask))
return -EINVAL;
for (i = 0; i < HASH_ALGO__LAST; i++) {
if (!strcmp(args[0].from, hash_algo_name[i])) {
opt->hash = i;
private->hash = i;
break;
}
}
if (i == HASH_ALGO__LAST)
return -EINVAL;
if (!tpm2 && i != HASH_ALGO_SHA1) {
if (!is_tpm2 && i != HASH_ALGO_SHA1) {
pr_info("TPM 1.x only supports SHA-1.\n");
return -EINVAL;
}
break;
case Opt_policydigest:
digest_len = hash_digest_size[opt->hash];
if (!tpm2 || strlen(args[0].from) != (2 * digest_len))
digest_len = hash_digest_size[private->hash];
if (!is_tpm2 ||
strlen(args[0].from) != (2 * digest_len))
return -EINVAL;
res = hex2bin(opt->policydigest, args[0].from,
res = hex2bin(private->policydigest, args[0].from,
digest_len);
if (res < 0)
return -EINVAL;
opt->policydigest_len = digest_len;
private->policydigest_len = digest_len;
break;
case Opt_policyhandle:
if (!tpm2)
if (!is_tpm2)
return -EINVAL;
res = kstrtoul(args[0].from, 16, &handle);
if (res < 0)
return -EINVAL;
opt->policyhandle = handle;
private->policyhandle = handle;
break;
default:
return -EINVAL;
@ -830,29 +834,33 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
return 0;
}
static struct trusted_key_options *trusted_options_alloc(void)
static struct trusted_key_options *trusted_options_alloc(bool is_tpm2)
{
struct trusted_key_tpm *private;
struct trusted_key_options *options;
int tpm2;
tpm2 = tpm_is_tpm2(chip);
if (tpm2 < 0)
return NULL;
options = kzalloc_obj(*options);
if (options) {
/* set any non-zero defaults */
options->keytype = SRK_keytype;
if (!tpm2)
options->keyhandle = SRKHANDLE;
private = kzalloc_obj(*private);
if (!private) {
kfree_sensitive(options);
options = NULL;
} else {
if (!is_tpm2)
private->keyhandle = SRKHANDLE;
options->private = private;
}
}
return options;
}
static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
{
struct trusted_key_options *options = NULL;
struct trusted_key_options *options __free(kfree_sensitive) = NULL;
struct trusted_key_tpm *private __free(kfree_sensitive) = NULL;
int ret = 0;
int tpm2;
@ -860,19 +868,19 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
if (tpm2 < 0)
return tpm2;
options = trusted_options_alloc();
options = trusted_options_alloc(tpm2);
if (!options)
return -ENOMEM;
ret = getoptions(datablob, p, options);
private = options->private;
ret = getoptions(datablob, p, options, tpm2);
if (ret < 0)
goto out;
return ret;
dump_options(options);
if (!options->keyhandle && !tpm2) {
ret = -EINVAL;
goto out;
}
if (!private->keyhandle && !tpm2)
return -EINVAL;
if (tpm2)
ret = tpm2_seal_trusted(chip, p, options);
@ -880,24 +888,24 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
ret = key_seal(p, options);
if (ret < 0) {
pr_info("key_seal failed (%d)\n", ret);
goto out;
return ret;
}
if (options->pcrlock) {
ret = pcrlock(options->pcrlock);
if (private->pcrlock) {
ret = pcrlock(private->pcrlock);
if (ret < 0) {
pr_info("pcrlock failed (%d)\n", ret);
goto out;
return ret;
}
}
out:
kfree_sensitive(options);
return ret;
}
static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
{
struct trusted_key_options *options = NULL;
struct trusted_key_options *options __free(kfree_sensitive) = NULL;
struct trusted_key_tpm *private __free(kfree_sensitive) = NULL;
int ret = 0;
int tpm2;
@ -905,36 +913,36 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
if (tpm2 < 0)
return tpm2;
options = trusted_options_alloc();
options = trusted_options_alloc(tpm2);
if (!options)
return -ENOMEM;
private = options->private;
ret = getoptions(datablob, p, options);
ret = getoptions(datablob, p, options, tpm2);
if (ret < 0)
goto out;
return ret;
dump_options(options);
if (!options->keyhandle && !tpm2) {
ret = -EINVAL;
goto out;
}
if (!private->keyhandle && !tpm2)
return -EINVAL;
if (tpm2)
ret = tpm2_unseal_trusted(chip, p, options);
else
ret = key_unseal(p, options);
if (ret < 0)
if (ret < 0) {
pr_info("key_unseal failed (%d)\n", ret);
return ret;
}
if (options->pcrlock) {
ret = pcrlock(options->pcrlock);
if (private->pcrlock) {
ret = pcrlock(private->pcrlock);
if (ret < 0) {
pr_info("pcrlock failed (%d)\n", ret);
goto out;
return ret;
}
}
out:
kfree_sensitive(options);
return ret;
}

View File

@ -23,36 +23,48 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
struct trusted_key_options *options,
u8 *src, u32 len)
{
struct trusted_key_tpm *private = options->private;
const int SCRATCH_SIZE = PAGE_SIZE;
u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
u8 *work = scratch, *work1;
u8 *end_work = scratch + SCRATCH_SIZE;
u8 *scratch __free(kfree) = NULL;
u8 *work, *work1;
u8 *end_work;
u8 *priv, *pub;
u16 priv_len, pub_len;
u32 priv_len, pub_len;
int ret;
priv_len = get_unaligned_be16(src) + 2;
priv = src;
if (len < 4)
return -EINVAL;
priv_len = get_unaligned_be16(src) + 2;
if (priv_len + 2 > len)
return -EIO;
priv = src;
src += priv_len;
pub_len = get_unaligned_be16(src) + 2;
if (pub_len + priv_len > len)
return -EIO;
pub = src;
scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
if (!scratch)
return -ENOMEM;
work = scratch;
end_work = scratch + SCRATCH_SIZE;
work = asn1_encode_oid(work, end_work, tpm2key_oid,
asn1_oid_len(tpm2key_oid));
if (options->blobauth_len == 0) {
if (private->blobauth_len == 0) {
unsigned char bool[3], *w = bool;
/* tag 0 is emptyAuth */
w = asn1_encode_boolean(w, w + sizeof(bool), true);
if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) {
ret = PTR_ERR(w);
goto err;
}
if (WARN(IS_ERR(w), "BUG: Boolean failed to encode"))
return PTR_ERR(w);
work = asn1_encode_tag(work, end_work, 0, bool, w - bool);
}
@ -64,11 +76,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
*/
if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE,
"BUG: scratch buffer is too small")) {
ret = -EINVAL;
goto err;
return -EINVAL;
}
work = asn1_encode_integer(work, end_work, options->keyhandle);
work = asn1_encode_integer(work, end_work, private->keyhandle);
work = asn1_encode_octet_string(work, end_work, pub, pub_len);
work = asn1_encode_octet_string(work, end_work, priv, priv_len);
@ -78,15 +89,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
if (IS_ERR(work1)) {
ret = PTR_ERR(work1);
pr_err("BUG: ASN.1 encoder failed with %d\n", ret);
goto err;
return ret;
}
kfree(scratch);
return work1 - payload->blob;
err:
kfree(scratch);
return ret;
}
struct tpm2_key_context {
@ -101,6 +107,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
struct trusted_key_options *options,
u8 **buf, unsigned int *blob_len)
{
struct trusted_key_tpm *private = options->private;
int ret;
struct tpm2_key_context ctx;
u8 *blob;
@ -115,13 +122,13 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE)
return -EINVAL;
blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL);
blob = kmalloc(ctx.priv_len + ctx.pub_len, GFP_KERNEL);
if (!blob)
return -ENOMEM;
*buf = blob;
*blob_len = ctx.priv_len + ctx.pub_len;
options->keyhandle = ctx.parent;
private->keyhandle = ctx.parent;
memcpy(blob, ctx.priv, ctx.priv_len);
blob += ctx.priv_len;
@ -233,6 +240,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_payload *payload,
struct trusted_key_options *options)
{
struct trusted_key_tpm *private = options->private;
off_t offset = TPM_HEADER_SIZE;
struct tpm_buf *buf __free(kfree) = NULL;
struct tpm_buf *sized __free(kfree) = NULL;
@ -241,11 +249,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
u32 flags;
int rc;
hash = tpm2_find_hash_alg(options->hash);
hash = tpm2_find_hash_alg(private->hash);
if (hash < 0)
return hash;
if (!options->keyhandle)
if (!private->keyhandle)
return -EINVAL;
rc = tpm_try_get_ops(chip);
@ -275,18 +283,18 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
tpm_buf_init_sized(sized, TPM_BUFSIZE);
rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
rc = tpm_buf_append_name(chip, buf, private->keyhandle, NULL);
if (rc)
goto out;
tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT,
options->keyauth, TPM_DIGEST_SIZE);
private->keyauth, TPM_DIGEST_SIZE);
/* sensitive */
tpm_buf_append_u16(sized, options->blobauth_len);
tpm_buf_append_u16(sized, private->blobauth_len);
if (options->blobauth_len)
tpm_buf_append(sized, options->blobauth, options->blobauth_len);
if (private->blobauth_len)
tpm_buf_append(sized, private->blobauth, private->blobauth_len);
tpm_buf_append_u16(sized, payload->key_len);
tpm_buf_append(sized, payload->key, payload->key_len);
@ -299,14 +307,15 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
/* key properties */
flags = 0;
flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
flags |= private->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
tpm_buf_append_u32(sized, flags);
/* policy */
tpm_buf_append_u16(sized, options->policydigest_len);
if (options->policydigest_len)
tpm_buf_append(sized, options->policydigest, options->policydigest_len);
tpm_buf_append_u16(sized, private->policydigest_len);
if (private->policydigest_len)
tpm_buf_append(sized, private->policydigest,
private->policydigest_len);
/* public parameters */
tpm_buf_append_u16(sized, TPM_ALG_NULL);
@ -336,10 +345,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
goto out;
blob_len = tpm_buf_read_u32(buf, &offset);
if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) {
if (buf->flags & TPM_BUF_INVALID) {
rc = -E2BIG;
goto out;
}
if (buf->length - offset < blob_len) {
rc = -EFAULT;
goto out;
@ -377,6 +387,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
u32 *blob_handle)
{
u8 *blob_ref __free(kfree) = NULL;
struct trusted_key_tpm *private = options->private;
struct tpm_buf *buf __free(kfree) = NULL;
unsigned int private_len;
unsigned int public_len;
@ -397,7 +408,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
}
/* new format carries keyhandle but old format doesn't */
if (!options->keyhandle)
if (!private->keyhandle)
return -EINVAL;
/* must be big enough for at least the two be16 size counts */
@ -441,11 +452,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
tpm_buf_init(buf, TPM_BUFSIZE);
tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
rc = tpm_buf_append_name(chip, buf, private->keyhandle, NULL);
if (rc)
return rc;
tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth,
tpm_buf_append_hmac_session(chip, buf, 0, private->keyauth,
TPM_DIGEST_SIZE);
tpm_buf_append(buf, blob, blob_len);
@ -485,6 +496,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
struct trusted_key_options *options,
u32 blob_handle)
{
struct trusted_key_tpm *private = options->private;
struct tpm_header *head;
struct tpm_buf *buf __free(kfree) = NULL;
u16 data_len;
@ -509,10 +521,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
if (rc)
return rc;
if (!options->policyhandle) {
if (!private->policyhandle) {
tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
options->blobauth,
options->blobauth_len);
private->blobauth,
private->blobauth_len);
} else {
/*
* FIXME: The policy session was generated outside the
@ -525,9 +537,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
* could repeat our actions with the exfiltrated
* password.
*/
tpm2_buf_append_auth(buf, options->policyhandle,
tpm2_buf_append_auth(buf, private->policyhandle,
NULL /* nonce */, 0, 0,
options->blobauth, options->blobauth_len);
private->blobauth, private->blobauth_len);
if (tpm2_chip_auth(chip)) {
tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
NULL, 0);