diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/integrity/digsig.c | 9 | ||||
-rw-r--r-- | security/integrity/ima/ima_mok.c | 11 | ||||
-rw-r--r-- | security/keys/Kconfig | 2 | ||||
-rw-r--r-- | security/keys/Makefile | 3 | ||||
-rw-r--r-- | security/keys/compat.c | 9 | ||||
-rw-r--r-- | security/keys/compat_dh.c | 38 | ||||
-rw-r--r-- | security/keys/dh.c | 220 | ||||
-rw-r--r-- | security/keys/gc.c | 11 | ||||
-rw-r--r-- | security/keys/internal.h | 29 | ||||
-rw-r--r-- | security/keys/key.c | 46 | ||||
-rw-r--r-- | security/keys/keyctl.c | 60 | ||||
-rw-r--r-- | security/keys/keyring.c | 179 |
12 files changed, 568 insertions, 49 deletions
diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 106e855e2d9d..06554c448dce 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -81,18 +81,25 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, int __init integrity_init_keyring(const unsigned int id) { const struct cred *cred = current_cred(); + struct key_restriction *restriction; int err = 0; if (!init_keyring) return 0; + restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + if (!restriction) + return -ENOMEM; + + restriction->check = restrict_link_to_ima; + keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0), KGIDT_INIT(0), cred, ((KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_WRITE | KEY_USR_SEARCH), KEY_ALLOC_NOT_IN_QUOTA, - restrict_link_to_ima, NULL); + restriction, NULL); if (IS_ERR(keyring[id])) { err = PTR_ERR(keyring[id]); pr_info("Can't allocate %s keyring (%d)\n", diff --git a/security/integrity/ima/ima_mok.c b/security/integrity/ima/ima_mok.c index 74a279957464..073ddc9bce5b 100644 --- a/security/integrity/ima/ima_mok.c +++ b/security/integrity/ima/ima_mok.c @@ -17,6 +17,7 @@ #include <linux/cred.h> #include <linux/err.h> #include <linux/init.h> +#include <linux/slab.h> #include <keys/system_keyring.h> @@ -27,15 +28,23 @@ struct key *ima_blacklist_keyring; */ __init int ima_mok_init(void) { + struct key_restriction *restriction; + pr_notice("Allocating IMA blacklist keyring.\n"); + restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + if (!restriction) + panic("Can't allocate IMA blacklist restriction."); + + restriction->check = restrict_link_by_builtin_trusted; + ima_blacklist_keyring = keyring_alloc(".ima_blacklist", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_WRITE | KEY_USR_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, - restrict_link_by_builtin_trusted, NULL); + restriction, NULL); if (IS_ERR(ima_blacklist_keyring)) panic("Can't allocate IMA blacklist keyring."); diff --git a/security/keys/Kconfig b/security/keys/Kconfig index d942c7c2bc0a..6fd95f76bfae 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -90,6 +90,8 @@ config KEY_DH_OPERATIONS bool "Diffie-Hellman operations on retained keys" depends on KEYS select MPILIB + select CRYPTO + select CRYPTO_HASH help This option provides support for calculating Diffie-Hellman public keys and shared secrets using values stored as keys diff --git a/security/keys/Makefile b/security/keys/Makefile index 1fd4a16e6daf..57dff0c15809 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -15,7 +15,8 @@ obj-y := \ request_key.o \ request_key_auth.o \ user_defined.o -obj-$(CONFIG_KEYS_COMPAT) += compat.o +compat-obj-$(CONFIG_KEY_DH_OPERATIONS) += compat_dh.o +obj-$(CONFIG_KEYS_COMPAT) += compat.o $(compat-obj-y) obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o diff --git a/security/keys/compat.c b/security/keys/compat.c index 36c80bf5b89c..e87c89c0177c 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -133,8 +133,13 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option, return keyctl_get_persistent(arg2, arg3); case KEYCTL_DH_COMPUTE: - return keyctl_dh_compute(compat_ptr(arg2), compat_ptr(arg3), - arg4, compat_ptr(arg5)); + return compat_keyctl_dh_compute(compat_ptr(arg2), + compat_ptr(arg3), + arg4, compat_ptr(arg5)); + + case KEYCTL_RESTRICT_KEYRING: + return keyctl_restrict_keyring(arg2, compat_ptr(arg3), + compat_ptr(arg4)); default: return -EOPNOTSUPP; diff --git a/security/keys/compat_dh.c b/security/keys/compat_dh.c new file mode 100644 index 000000000000..a6a659b6bcb6 --- /dev/null +++ b/security/keys/compat_dh.c @@ -0,0 +1,38 @@ +/* 32-bit compatibility syscall for 64-bit systems for DH operations + * + * Copyright (C) 2016 Stephan Mueller <smueller@chronox.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/uaccess.h> + +#include "internal.h" + +/* + * Perform the DH computation or DH based key derivation. + * + * If successful, 0 will be returned. + */ +long compat_keyctl_dh_compute(struct keyctl_dh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_kdf_params __user *kdf) +{ + struct keyctl_kdf_params kdfcopy; + struct compat_keyctl_kdf_params compat_kdfcopy; + + if (!kdf) + return __keyctl_dh_compute(params, buffer, buflen, NULL); + + if (copy_from_user(&compat_kdfcopy, kdf, sizeof(compat_kdfcopy)) != 0) + return -EFAULT; + + kdfcopy.hashname = compat_ptr(compat_kdfcopy.hashname); + kdfcopy.otherinfo = compat_ptr(compat_kdfcopy.otherinfo); + kdfcopy.otherinfolen = compat_kdfcopy.otherinfolen; + + return __keyctl_dh_compute(params, buffer, buflen, &kdfcopy); +} diff --git a/security/keys/dh.c b/security/keys/dh.c index 893af4c45038..e603bd912e4c 100644 --- a/security/keys/dh.c +++ b/security/keys/dh.c @@ -11,6 +11,8 @@ #include <linux/mpi.h> #include <linux/slab.h> #include <linux/uaccess.h> +#include <linux/crypto.h> +#include <crypto/hash.h> #include <keys/user-type.h> #include "internal.h" @@ -77,9 +79,146 @@ error: return ret; } -long keyctl_dh_compute(struct keyctl_dh_params __user *params, - char __user *buffer, size_t buflen, - void __user *reserved) +struct kdf_sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static int kdf_alloc(struct kdf_sdesc **sdesc_ret, char *hashname) +{ + struct crypto_shash *tfm; + struct kdf_sdesc *sdesc; + int size; + + /* allocate synchronous hash */ + tfm = crypto_alloc_shash(hashname, 0, 0); + if (IS_ERR(tfm)) { + pr_info("could not allocate digest TFM handle %s\n", hashname); + return PTR_ERR(tfm); + } + + size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return -ENOMEM; + sdesc->shash.tfm = tfm; + sdesc->shash.flags = 0x0; + + *sdesc_ret = sdesc; + + return 0; +} + +static void kdf_dealloc(struct kdf_sdesc *sdesc) +{ + if (!sdesc) + return; + + if (sdesc->shash.tfm) + crypto_free_shash(sdesc->shash.tfm); + + kzfree(sdesc); +} + +/* convert 32 bit integer into its string representation */ +static inline void crypto_kw_cpu_to_be32(u32 val, u8 *buf) +{ + __be32 *a = (__be32 *)buf; + + *a = cpu_to_be32(val); +} + +/* + * Implementation of the KDF in counter mode according to SP800-108 section 5.1 + * as well as SP800-56A section 5.8.1 (Single-step KDF). + * + * SP800-56A: + * The src pointer is defined as Z || other info where Z is the shared secret + * from DH and other info is an arbitrary string (see SP800-56A section + * 5.8.1.2). + */ +static int kdf_ctr(struct kdf_sdesc *sdesc, const u8 *src, unsigned int slen, + u8 *dst, unsigned int dlen) +{ + struct shash_desc *desc = &sdesc->shash; + unsigned int h = crypto_shash_digestsize(desc->tfm); + int err = 0; + u8 *dst_orig = dst; + u32 i = 1; + u8 iteration[sizeof(u32)]; + + while (dlen) { + err = crypto_shash_init(desc); + if (err) + goto err; + + crypto_kw_cpu_to_be32(i, iteration); + err = crypto_shash_update(desc, iteration, sizeof(u32)); + if (err) + goto err; + + if (src && slen) { + err = crypto_shash_update(desc, src, slen); + if (err) + goto err; + } + + if (dlen < h) { + u8 tmpbuffer[h]; + + err = crypto_shash_final(desc, tmpbuffer); + if (err) + goto err; + memcpy(dst, tmpbuffer, dlen); + memzero_explicit(tmpbuffer, h); + return 0; + } else { + err = crypto_shash_final(desc, dst); + if (err) + goto err; + + dlen -= h; + dst += h; + i++; + } + } + + return 0; + +err: + memzero_explicit(dst_orig, dlen); + return err; +} + +static int keyctl_dh_compute_kdf(struct kdf_sdesc *sdesc, + char __user *buffer, size_t buflen, + uint8_t *kbuf, size_t kbuflen) +{ + uint8_t *outbuf = NULL; + int ret; + + outbuf = kmalloc(buflen, GFP_KERNEL); + if (!outbuf) { + ret = -ENOMEM; + goto err; + } + + ret = kdf_ctr(sdesc, kbuf, kbuflen, outbuf, buflen); + if (ret) + goto err; + + ret = buflen; + if (copy_to_user(buffer, outbuf, buflen) != 0) + ret = -EFAULT; + +err: + kzfree(outbuf); + return ret; +} + +long __keyctl_dh_compute(struct keyctl_dh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_kdf_params *kdfcopy) { long ret; MPI base, private, prime, result; @@ -88,6 +227,7 @@ long keyctl_dh_compute(struct keyctl_dh_params __user *params, uint8_t *kbuf; ssize_t keylen; size_t resultlen; + struct kdf_sdesc *sdesc = NULL; if (!params || (!buffer && buflen)) { ret = -EINVAL; @@ -98,12 +238,34 @@ long keyctl_dh_compute(struct keyctl_dh_params __user *params, goto out; } - if (reserved) { - ret = -EINVAL; - goto out; + if (kdfcopy) { + char *hashname; + + if (buflen > KEYCTL_KDF_MAX_OUTPUT_LEN || + kdfcopy->otherinfolen > KEYCTL_KDF_MAX_OI_LEN) { + ret = -EMSGSIZE; + goto out; + } + + /* get KDF name string */ + hashname = strndup_user(kdfcopy->hashname, CRYPTO_MAX_ALG_NAME); + if (IS_ERR(hashname)) { + ret = PTR_ERR(hashname); + goto out; + } + + /* allocate KDF from the kernel crypto API */ + ret = kdf_alloc(&sdesc, hashname); + kfree(hashname); + if (ret) + goto out; } - keylen = mpi_from_key(pcopy.prime, buflen, &prime); + /* + * If the caller requests postprocessing with a KDF, allow an + * arbitrary output buffer size since the KDF ensures proper truncation. + */ + keylen = mpi_from_key(pcopy.prime, kdfcopy ? SIZE_MAX : buflen, &prime); if (keylen < 0 || !prime) { /* buflen == 0 may be used to query the required buffer size, * which is the prime key length. @@ -133,12 +295,25 @@ long keyctl_dh_compute(struct keyctl_dh_params __user *params, goto error3; } - kbuf = kmalloc(resultlen, GFP_KERNEL); + /* allocate space for DH shared secret and SP800-56A otherinfo */ + kbuf = kmalloc(kdfcopy ? (resultlen + kdfcopy->otherinfolen) : resultlen, + GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; goto error4; } + /* + * Concatenate SP800-56A otherinfo past DH shared secret -- the + * input to the KDF is (DH shared secret || otherinfo) + */ + if (kdfcopy && kdfcopy->otherinfo && + copy_from_user(kbuf + resultlen, kdfcopy->otherinfo, + kdfcopy->otherinfolen) != 0) { + ret = -EFAULT; + goto error5; + } + ret = do_dh(result, base, private, prime); if (ret) goto error5; @@ -147,12 +322,17 @@ long keyctl_dh_compute(struct keyctl_dh_params __user *params, if (ret != 0) goto error5; - ret = nbytes; - if (copy_to_user(buffer, kbuf, nbytes) != 0) - ret = -EFAULT; + if (kdfcopy) { + ret = keyctl_dh_compute_kdf(sdesc, buffer, buflen, kbuf, + resultlen + kdfcopy->otherinfolen); + } else { + ret = nbytes; + if (copy_to_user(buffer, kbuf, nbytes) != 0) + ret = -EFAULT; + } error5: - kfree(kbuf); + kzfree(kbuf); error4: mpi_free(result); error3: @@ -162,5 +342,21 @@ error2: error1: mpi_free(prime); out: + kdf_dealloc(sdesc); return ret; } + +long keyctl_dh_compute(struct keyctl_dh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_kdf_params __user *kdf) +{ + struct keyctl_kdf_params kdfcopy; + + if (!kdf) + return __keyctl_dh_compute(params, buffer, buflen, NULL); + + if (copy_from_user(&kdfcopy, kdf, sizeof(kdfcopy)) != 0) + return -EFAULT; + + return __keyctl_dh_compute(params, buffer, buflen, &kdfcopy); +} diff --git a/security/keys/gc.c b/security/keys/gc.c index 44789256c88c..15b9ddf510e4 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -229,6 +229,9 @@ continue_scanning: set_bit(KEY_FLAG_DEAD, &key->flags); key->perm = 0; goto skip_dead_key; + } else if (key->type == &key_type_keyring && + key->restrict_link) { + goto found_restricted_keyring; } } @@ -334,6 +337,14 @@ found_unreferenced_key: gc_state |= KEY_GC_REAP_AGAIN; goto maybe_resched; + /* We found a restricted keyring and need to update the restriction if + * it is associated with the dead key type. + */ +found_restricted_keyring: + spin_unlock(&key_serial_lock); + keyring_restriction_gc(key, key_gc_dead_keytype); + goto maybe_resched; + /* We found a keyring and we need to check the payload for links to * dead or expired keys. We don't flag another reap immediately as we * have to wait for the old payload to be destroyed by RCU before we diff --git a/security/keys/internal.h b/security/keys/internal.h index 6bee06ae026d..c0f8682eba69 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -18,6 +18,7 @@ #include <linux/task_work.h> #include <linux/keyctl.h> #include <linux/refcount.h> +#include <linux/compat.h> struct iovec; @@ -168,6 +169,8 @@ extern void key_change_session_keyring(struct callback_head *twork); extern struct work_struct key_gc_work; extern unsigned key_gc_delay; extern void keyring_gc(struct key *keyring, time_t limit); +extern void keyring_restriction_gc(struct key *keyring, + struct key_type *dead_type); extern void key_schedule_gc(time_t gc_at); extern void key_schedule_gc_links(void); extern void key_gc_keytype(struct key_type *ktype); @@ -250,6 +253,9 @@ struct iov_iter; extern long keyctl_instantiate_key_common(key_serial_t, struct iov_iter *, key_serial_t); +extern long keyctl_restrict_keyring(key_serial_t id, + const char __user *_type, + const char __user *_restriction); #ifdef CONFIG_PERSISTENT_KEYRINGS extern long keyctl_get_persistent(uid_t, key_serial_t); extern unsigned persistent_keyring_expiry; @@ -262,15 +268,34 @@ static inline long keyctl_get_persistent(uid_t uid, key_serial_t destring) #ifdef CONFIG_KEY_DH_OPERATIONS extern long keyctl_dh_compute(struct keyctl_dh_params __user *, char __user *, - size_t, void __user *); + size_t, struct keyctl_kdf_params __user *); +extern long __keyctl_dh_compute(struct keyctl_dh_params __user *, char __user *, + size_t, struct keyctl_kdf_params *); +#ifdef CONFIG_KEYS_COMPAT +extern long compat_keyctl_dh_compute(struct keyctl_dh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_kdf_params __user *kdf); +#endif +#define KEYCTL_KDF_MAX_OUTPUT_LEN 1024 /* max length of KDF output */ +#define KEYCTL_KDF_MAX_OI_LEN 64 /* max length of otherinfo */ #else static inline long keyctl_dh_compute(struct keyctl_dh_params __user *params, char __user *buffer, size_t buflen, - void __user *reserved) + struct keyctl_kdf_params __user *kdf) +{ + return -EOPNOTSUPP; +} + +#ifdef CONFIG_KEYS_COMPAT +static inline long compat_keyctl_dh_compute( + struct keyctl_dh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_kdf_params __user *kdf) { return -EOPNOTSUPP; } #endif +#endif /* * Debugging key validation diff --git a/security/keys/key.c b/security/keys/key.c index b4958b36fa27..455c04d80bbb 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -201,12 +201,15 @@ serial_exists: * @cred: The credentials specifying UID namespace. * @perm: The permissions mask of the new key. * @flags: Flags specifying quota properties. - * @restrict_link: Optional link restriction method for new keyrings. + * @restrict_link: Optional link restriction for new keyrings. * * Allocate a key of the specified type with the attributes given. The key is * returned in an uninstantiated state and the caller needs to instantiate the * key before returning. * + * The restrict_link structure (if not NULL) will be freed when the + * keyring is destroyed, so it must be dynamically allocated. + * * The user's key count quota is updated to reflect the creation of the key and * the user's key data quota has the default for the key type reserved. The * instantiation function should amend this as necessary. If insufficient @@ -225,9 +228,7 @@ serial_exists: struct key *key_alloc(struct key_type *type, const char *desc, kuid_t uid, kgid_t gid, const struct cred *cred, key_perm_t perm, unsigned long flags, - int (*restrict_link)(struct key *, - const struct key_type *, - const union key_payload *)) + struct key_restriction *restrict_link) { struct key_user *user = NULL; struct key *key; @@ -499,19 +500,23 @@ int key_instantiate_and_link(struct key *key, } if (keyring) { - if (keyring->restrict_link) { - ret = keyring->restrict_link(keyring, key->type, - &prep.payload); - if (ret < 0) - goto error; - } ret = __key_link_begin(keyring, &key->index_key, &edit); if (ret < 0) goto error; + + if (keyring->restrict_link && keyring->restrict_link->check) { + struct key_restriction *keyres = keyring->restrict_link; + + ret = keyres->check(keyring, key->type, &prep.payload, + keyres->key); + if (ret < 0) + goto error_link_end; + } } ret = __key_instantiate_and_link(key, &prep, keyring, authkey, &edit); +error_link_end: if (keyring) __key_link_end(keyring, &key->index_key, edit); @@ -806,9 +811,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, struct key *keyring, *key = NULL; key_ref_t key_ref; int ret; - int (*restrict_link)(struct key *, - const struct key_type *, - const union key_payload *) = NULL; + struct key_restriction *restrict_link = NULL; /* look up the key type to see if it's one of the registered kernel * types */ @@ -854,20 +857,21 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, } index_key.desc_len = strlen(index_key.description); - if (restrict_link) { - ret = restrict_link(keyring, index_key.type, &prep.payload); - if (ret < 0) { - key_ref = ERR_PTR(ret); - goto error_free_prep; - } - } - ret = __key_link_begin(keyring, &index_key, &edit); if (ret < 0) { key_ref = ERR_PTR(ret); goto error_free_prep; } + if (restrict_link && restrict_link->check) { + ret = restrict_link->check(keyring, index_key.type, + &prep.payload, restrict_link->key); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_link_end; + } + } + /* if we're going to allocate a new key, we're going to have * to modify the keyring */ ret = key_permission(keyring_ref, KEY_NEED_WRITE); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 52c34532c785..10fcea154c0f 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1583,6 +1583,59 @@ error_keyring: } /* + * Apply a restriction to a given keyring. + * + * The caller must have Setattr permission to change keyring restrictions. + * + * The requested type name may be a NULL pointer to reject all attempts + * to link to the keyring. If _type is non-NULL, _restriction can be + * NULL or a pointer to a string describing the restriction. If _type is + * NULL, _restriction must also be NULL. + * + * Returns 0 if successful. + */ +long keyctl_restrict_keyring(key_serial_t id, const char __user *_type, + const char __user *_restriction) +{ + key_ref_t key_ref; + bool link_reject = !_type; + char type[32]; + char *restriction = NULL; + long ret; + + key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR); + if (IS_ERR(key_ref)) + return PTR_ERR(key_ref); + + if (_type) { + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + } + + if (_restriction) { + if (!_type) { + ret = -EINVAL; + goto error; + } + + restriction = strndup_user(_restriction, PAGE_SIZE); + if (IS_ERR(restriction)) { + ret = PTR_ERR(restriction); + goto error; + } + } + + ret = keyring_restrict(key_ref, link_reject ? NULL : type, restriction); + kfree(restriction); + +error: + key_ref_put(key_ref); + + return ret; +} + +/* * The key control system call */ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, @@ -1691,7 +1744,12 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, case KEYCTL_DH_COMPUTE: return keyctl_dh_compute((struct keyctl_dh_params __user *) arg2, (char __user *) arg3, (size_t) arg4, - (void __user *) arg5); + (struct keyctl_kdf_params __user *) arg5); + + case KEYCTL_RESTRICT_KEYRING: + return keyctl_restrict_keyring((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4); default: return -EOPNOTSUPP; diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 3d95f7d02ba1..4d1678e4586f 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -394,6 +394,13 @@ static void keyring_destroy(struct key *keyring) write_unlock(&keyring_name_lock); } + if (keyring->restrict_link) { + struct key_restriction *keyres = keyring->restrict_link; + + key_put(keyres->key); + kfree(keyres); + } + assoc_array_destroy(&keyring->keys, &keyring_assoc_array_ops); } @@ -492,9 +499,7 @@ static long keyring_read(const struct key *keyring, struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, const struct cred *cred, key_perm_t perm, unsigned long flags, - int (*restrict_link)(struct key *, - const struct key_type *, - const union key_payload *), + struct key_restriction *restrict_link, struct key *dest) { struct key *keyring; @@ -519,17 +524,19 @@ EXPORT_SYMBOL(keyring_alloc); * @keyring: The keyring being added to. * @type: The type of key being added. * @payload: The payload of the key intended to be added. + * @data: Additional data for evaluating restriction. * * Reject the addition of any links to a keyring. It can be overridden by * passing KEY_ALLOC_BYPASS_RESTRICTION to key_instantiate_and_link() when * adding a key to a keyring. * - * This is meant to be passed as the restrict_link parameter to - * keyring_alloc(). + * This is meant to be stored in a key_restriction structure which is passed + * in the restrict_link parameter to keyring_alloc(). */ int restrict_link_reject(struct key *keyring, const struct key_type *type, - const union key_payload *payload) + const union key_payload *payload, + struct key *restriction_key) { return -EPERM; } @@ -940,6 +947,111 @@ key_ref_t keyring_search(key_ref_t keyring, } EXPORT_SYMBOL(keyring_search); +static struct key_restriction *keyring_restriction_alloc( + key_restrict_link_func_t check) +{ + struct key_restriction *keyres = + kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + + if (!keyres) + return ERR_PTR(-ENOMEM); + + keyres->check = check; + + return keyres; +} + +/* + * Semaphore to serialise restriction setup to prevent reference count + * cycles through restriction key pointers. + */ +static DECLARE_RWSEM(keyring_serialise_restrict_sem); + +/* + * Check for restriction cycles that would prevent keyring garbage collection. + * keyring_serialise_restrict_sem must be held. + */ +static bool keyring_detect_restriction_cycle(const struct key *dest_keyring, + struct key_restriction *keyres) +{ + while (keyres && keyres->key && + keyres->key->type == &key_type_keyring) { + if (keyres->key == dest_keyring) + return true; + + keyres = keyres->key->restrict_link; + } + + return false; +} + +/** + * keyring_restrict - Look up and apply a restriction to a keyring + * + * @keyring: The keyring to be restricted + * @restriction: The restriction options to apply to the keyring + */ +int keyring_restrict(key_ref_t keyring_ref, const char *type, + const char *restriction) +{ + struct key *keyring; + struct key_type *restrict_type = NULL; + struct key_restriction *restrict_link; + int ret = 0; + + keyring = key_ref_to_ptr(keyring_ref); + key_check(keyring); + + if (keyring->type != &key_type_keyring) + return -ENOTDIR; + + if (!type) { + restrict_link = keyring_restriction_alloc(restrict_link_reject); + } else { + restrict_type = key_type_lookup(type); + + if (IS_ERR(restrict_type)) + return PTR_ERR(restrict_type); + + if (!restrict_type->lookup_restriction) { + ret = -ENOENT; + goto error; + } + + restrict_link = restrict_type->lookup_restriction(restriction); + } + + if (IS_ERR(restrict_link)) { + ret = PTR_ERR(restrict_link); + goto error; + } + + down_write(&keyring->sem); + down_write(&keyring_serialise_restrict_sem); + + if (keyring->restrict_link) + ret = -EEXIST; + else if (keyring_detect_restriction_cycle(keyring, restrict_link)) + ret = -EDEADLK; + else + keyring->restrict_link = restrict_link; + + up_write(&keyring_serialise_restrict_sem); + up_write(&keyring->sem); + + if (ret < 0) { + key_put(restrict_link->key); + kfree(restrict_link); + } + +error: + if (restrict_type) + key_type_put(restrict_type); + + return ret; +} +EXPORT_SYMBOL(keyring_restrict); + /* * Search the given keyring for a key that might be updated. * @@ -1220,9 +1332,10 @@ void __key_link_end(struct key *keyring, */ static int __key_link_check_restriction(struct key *keyring, struct key *key) { - if (!keyring->restrict_link) + if (!keyring->restrict_link || !keyring->restrict_link->check) return 0; - return keyring->restrict_link(keyring, key->type, &key->payload); + return keyring->restrict_link->check(keyring, key->type, &key->payload, + keyring->restrict_link->key); } /** @@ -1426,3 +1539,53 @@ do_gc: up_write(&keyring->sem); kleave(" [gc]"); } + +/* + * Garbage collect restriction pointers from a keyring. + * + * Keyring restrictions are associated with a key type, and must be cleaned + * up if the key type is unregistered. The restriction is altered to always + * reject additional keys so a keyring cannot be opened up by unregistering + * a key type. + * + * Not called with any keyring locks held. The keyring's key struct will not + * be deallocated under us as only our caller may deallocate it. + * + * The caller is required to hold key_types_sem and dead_type->sem. This is + * fulfilled by key_gc_keytype() holding the locks on behalf of + * key_garbage_collector(), which it invokes on a workqueue. + */ +void keyring_restriction_gc(struct key *keyring, struct key_type *dead_type) +{ + struct key_restriction *keyres; + + kenter("%x{%s}", keyring->serial, keyring->description ?: ""); + + /* + * keyring->restrict_link is only assigned at key allocation time + * or with the key type locked, so the only values that could be + * concurrently assigned to keyring->restrict_link are for key + * types other than dead_type. Given this, it's ok to check + * the key type before acquiring keyring->sem. + */ + if (!dead_type || !keyring->restrict_link || + keyring->restrict_link->keytype != dead_type) { + kleave(" [no restriction gc]"); + return; + } + + /* Lock the keyring to ensure that a link is not in progress */ + down_write(&keyring->sem); + + keyres = keyring->restrict_link; + + keyres->check = restrict_link_reject; + + key_put(keyres->key); + keyres->key = NULL; + keyres->keytype = NULL; + + up_write(&keyring->sem); + + kleave(" [restriction gc]"); +} |