diff options
author | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
commit | 871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch) | |
tree | 8718f573808810c2a1e8cb8fb6ac469093ca2784 /security/keys | |
parent | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff) | |
download | FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2 FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip |
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized.
Changes are basically to make it look like kernel structure.
Diffstat (limited to 'security/keys')
-rw-r--r-- | security/keys/Makefile | 20 | ||||
-rw-r--r-- | security/keys/compat.c | 141 | ||||
-rw-r--r-- | security/keys/encrypted-keys/Makefile | 10 | ||||
-rw-r--r-- | security/keys/encrypted-keys/ecryptfs_format.c | 81 | ||||
-rw-r--r-- | security/keys/encrypted-keys/ecryptfs_format.h | 30 | ||||
-rw-r--r-- | security/keys/encrypted-keys/encrypted.c | 1038 | ||||
-rw-r--r-- | security/keys/encrypted-keys/encrypted.h | 66 | ||||
-rw-r--r-- | security/keys/encrypted-keys/masterkey_trusted.c | 47 | ||||
-rw-r--r-- | security/keys/gc.c | 390 | ||||
-rw-r--r-- | security/keys/internal.h | 251 | ||||
-rw-r--r-- | security/keys/key.c | 1031 | ||||
-rw-r--r-- | security/keys/keyctl.c | 1628 | ||||
-rw-r--r-- | security/keys/keyring.c | 1232 | ||||
-rw-r--r-- | security/keys/permission.c | 118 | ||||
-rw-r--r-- | security/keys/proc.c | 352 | ||||
-rw-r--r-- | security/keys/process_keys.c | 878 | ||||
-rw-r--r-- | security/keys/request_key.c | 713 | ||||
-rw-r--r-- | security/keys/request_key_auth.c | 267 | ||||
-rw-r--r-- | security/keys/sysctl.c | 65 | ||||
-rw-r--r-- | security/keys/trusted.c | 1191 | ||||
-rw-r--r-- | security/keys/trusted.h | 134 | ||||
-rw-r--r-- | security/keys/user_defined.c | 228 |
22 files changed, 9911 insertions, 0 deletions
diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 00000000..a56f1ffd --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for key management +# + +obj-y := \ + gc.o \ + key.o \ + keyring.o \ + keyctl.o \ + permission.o \ + process_keys.o \ + request_key.o \ + request_key_auth.o \ + user_defined.o + +obj-$(CONFIG_TRUSTED_KEYS) += trusted.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/ +obj-$(CONFIG_KEYS_COMPAT) += compat.o +obj-$(CONFIG_PROC_FS) += proc.o +obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/compat.c b/security/keys/compat.c new file mode 100644 index 00000000..4c48e134 --- /dev/null +++ b/security/keys/compat.c @@ -0,0 +1,141 @@ +/* 32-bit compatibility syscall for 64-bit systems + * + * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/syscalls.h> +#include <linux/keyctl.h> +#include <linux/compat.h> +#include <linux/slab.h> +#include "internal.h" + +/* + * Instantiate a key with the specified compatibility multipart payload and + * link the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long compat_keyctl_instantiate_key_iov( + key_serial_t id, + const struct compat_iovec __user *_payload_iov, + unsigned ioc, + key_serial_t ringid) +{ + struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; + long ret; + + if (_payload_iov == 0 || ioc == 0) + goto no_payload; + + ret = compat_rw_copy_check_uvector(WRITE, _payload_iov, ioc, + ARRAY_SIZE(iovstack), + iovstack, &iov, 1); + if (ret < 0) + return ret; + if (ret == 0) + goto no_payload_free; + + ret = keyctl_instantiate_key_common(id, iov, ioc, ret, ringid); + + if (iov != iovstack) + kfree(iov); + return ret; + +no_payload_free: + if (iov != iovstack) + kfree(iov); +no_payload: + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * The key control system call, 32-bit compatibility version for 64-bit archs + * + * This should only be called if the 64-bit arch uses weird pointers in 32-bit + * mode or doesn't guarantee that the top 32-bits of the argument registers on + * taking a 32-bit syscall are zero. If you can, you should call sys_keyctl() + * directly. + */ +asmlinkage long compat_sys_keyctl(u32 option, + u32 arg2, u32 arg3, u32 arg4, u32 arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID(arg2, arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring(compat_ptr(arg2)); + + case KEYCTL_UPDATE: + return keyctl_update_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key(arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear(arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link(arg2, arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink(arg2, arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search(arg2, compat_ptr(arg3), + compat_ptr(arg4), arg5); + + case KEYCTL_READ: + return keyctl_read_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key(arg2, arg3, arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key(arg2, arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key(arg2, compat_ptr(arg3), arg4, + arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key(arg2, arg3, arg4); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout(arg2, arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority(arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_SESSION_TO_PARENT: + return keyctl_session_to_parent(); + + case KEYCTL_REJECT: + return keyctl_reject_key(arg2, arg3, arg4, arg5); + + case KEYCTL_INSTANTIATE_IOV: + return compat_keyctl_instantiate_key_iov( + arg2, compat_ptr(arg3), arg4, arg5); + + default: + return -EOPNOTSUPP; + } +} diff --git a/security/keys/encrypted-keys/Makefile b/security/keys/encrypted-keys/Makefile new file mode 100644 index 00000000..d6f84332 --- /dev/null +++ b/security/keys/encrypted-keys/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for encrypted keys +# + +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys.o + +encrypted-keys-y := encrypted.o ecryptfs_format.o +masterkey-$(CONFIG_TRUSTED_KEYS) := masterkey_trusted.o +masterkey-$(CONFIG_TRUSTED_KEYS)-$(CONFIG_ENCRYPTED_KEYS) := masterkey_trusted.o +encrypted-keys-y += $(masterkey-y) $(masterkey-m-m) diff --git a/security/keys/encrypted-keys/ecryptfs_format.c b/security/keys/encrypted-keys/ecryptfs_format.c new file mode 100644 index 00000000..6daa3b6f --- /dev/null +++ b/security/keys/encrypted-keys/ecryptfs_format.c @@ -0,0 +1,81 @@ +/* + * ecryptfs_format.c: helper functions for the encrypted key type + * + * Copyright (C) 2006 International Business Machines Corp. + * Copyright (C) 2010 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Authors: + * Michael A. Halcrow <mahalcro@us.ibm.com> + * Tyler Hicks <tyhicks@ou.edu> + * Roberto Sassu <roberto.sassu@polito.it> + * + * 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, version 2 of the License. + */ + +#include <linux/module.h> +#include "ecryptfs_format.h" + +u8 *ecryptfs_get_auth_tok_key(struct ecryptfs_auth_tok *auth_tok) +{ + return auth_tok->token.password.session_key_encryption_key; +} +EXPORT_SYMBOL(ecryptfs_get_auth_tok_key); + +/* + * ecryptfs_get_versions() + * + * Source code taken from the software 'ecryptfs-utils' version 83. + * + */ +void ecryptfs_get_versions(int *major, int *minor, int *file_version) +{ + *major = ECRYPTFS_VERSION_MAJOR; + *minor = ECRYPTFS_VERSION_MINOR; + if (file_version) + *file_version = ECRYPTFS_SUPPORTED_FILE_VERSION; +} +EXPORT_SYMBOL(ecryptfs_get_versions); + +/* + * ecryptfs_fill_auth_tok - fill the ecryptfs_auth_tok structure + * + * Fill the ecryptfs_auth_tok structure with required ecryptfs data. + * The source code is inspired to the original function generate_payload() + * shipped with the software 'ecryptfs-utils' version 83. + * + */ +int ecryptfs_fill_auth_tok(struct ecryptfs_auth_tok *auth_tok, + const char *key_desc) +{ + int major, minor; + + ecryptfs_get_versions(&major, &minor, NULL); + auth_tok->version = (((uint16_t)(major << 8) & 0xFF00) + | ((uint16_t)minor & 0x00FF)); + auth_tok->token_type = ECRYPTFS_PASSWORD; + strncpy((char *)auth_tok->token.password.signature, key_desc, + ECRYPTFS_PASSWORD_SIG_SIZE); + auth_tok->token.password.session_key_encryption_key_bytes = + ECRYPTFS_MAX_KEY_BYTES; + /* + * Removed auth_tok->token.password.salt and + * auth_tok->token.password.session_key_encryption_key + * initialization from the original code + */ + /* TODO: Make the hash parameterizable via policy */ + auth_tok->token.password.flags |= + ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET; + /* The kernel code will encrypt the session key. */ + auth_tok->session_key.encrypted_key[0] = 0; + auth_tok->session_key.encrypted_key_size = 0; + /* Default; subject to change by kernel eCryptfs */ + auth_tok->token.password.hash_algo = PGP_DIGEST_ALGO_SHA512; + auth_tok->token.password.flags &= ~(ECRYPTFS_PERSISTENT_PASSWORD); + return 0; +} +EXPORT_SYMBOL(ecryptfs_fill_auth_tok); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/encrypted-keys/ecryptfs_format.h b/security/keys/encrypted-keys/ecryptfs_format.h new file mode 100644 index 00000000..40294de2 --- /dev/null +++ b/security/keys/encrypted-keys/ecryptfs_format.h @@ -0,0 +1,30 @@ +/* + * ecryptfs_format.h: helper functions for the encrypted key type + * + * Copyright (C) 2006 International Business Machines Corp. + * Copyright (C) 2010 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Authors: + * Michael A. Halcrow <mahalcro@us.ibm.com> + * Tyler Hicks <tyhicks@ou.edu> + * Roberto Sassu <roberto.sassu@polito.it> + * + * 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, version 2 of the License. + */ + +#ifndef __KEYS_ECRYPTFS_H +#define __KEYS_ECRYPTFS_H + +#include <linux/ecryptfs.h> + +#define PGP_DIGEST_ALGO_SHA512 10 + +u8 *ecryptfs_get_auth_tok_key(struct ecryptfs_auth_tok *auth_tok); +void ecryptfs_get_versions(int *major, int *minor, int *file_version); +int ecryptfs_fill_auth_tok(struct ecryptfs_auth_tok *auth_tok, + const char *key_desc); + +#endif /* __KEYS_ECRYPTFS_H */ diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c new file mode 100644 index 00000000..2d1bb8af --- /dev/null +++ b/security/keys/encrypted-keys/encrypted.c @@ -0,0 +1,1038 @@ +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (C) 2010 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Roberto Sassu <roberto.sassu@polito.it> + * + * 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, version 2 of the License. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/string.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <keys/trusted-type.h> +#include <keys/encrypted-type.h> +#include <linux/key-type.h> +#include <linux/random.h> +#include <linux/rcupdate.h> +#include <linux/scatterlist.h> +#include <linux/crypto.h> +#include <linux/ctype.h> +#include <crypto/hash.h> +#include <crypto/sha.h> +#include <crypto/aes.h> + +#include "encrypted.h" +#include "ecryptfs_format.h" + +static const char KEY_TRUSTED_PREFIX[] = "trusted:"; +static const char KEY_USER_PREFIX[] = "user:"; +static const char hash_alg[] = "sha256"; +static const char hmac_alg[] = "hmac(sha256)"; +static const char blkcipher_alg[] = "cbc(aes)"; +static const char key_format_default[] = "default"; +static const char key_format_ecryptfs[] = "ecryptfs"; +static unsigned int ivsize; +static int blksize; + +#define KEY_TRUSTED_PREFIX_LEN (sizeof (KEY_TRUSTED_PREFIX) - 1) +#define KEY_USER_PREFIX_LEN (sizeof (KEY_USER_PREFIX) - 1) +#define KEY_ECRYPTFS_DESC_LEN 16 +#define HASH_SIZE SHA256_DIGEST_SIZE +#define MAX_DATA_SIZE 4096 +#define MIN_DATA_SIZE 20 + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +enum { + Opt_err = -1, Opt_new, Opt_load, Opt_update +}; + +enum { + Opt_error = -1, Opt_default, Opt_ecryptfs +}; + +static const match_table_t key_format_tokens = { + {Opt_default, "default"}, + {Opt_ecryptfs, "ecryptfs"}, + {Opt_error, NULL} +}; + +static const match_table_t key_tokens = { + {Opt_new, "new"}, + {Opt_load, "load"}, + {Opt_update, "update"}, + {Opt_err, NULL} +}; + +static int aes_get_sizes(void) +{ + struct crypto_blkcipher *tfm; + + tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + pr_err("encrypted_key: failed to alloc_cipher (%ld)\n", + PTR_ERR(tfm)); + return PTR_ERR(tfm); + } + ivsize = crypto_blkcipher_ivsize(tfm); + blksize = crypto_blkcipher_blocksize(tfm); + crypto_free_blkcipher(tfm); + return 0; +} + +/* + * valid_ecryptfs_desc - verify the description of a new/loaded encrypted key + * + * The description of a encrypted key with format 'ecryptfs' must contain + * exactly 16 hexadecimal characters. + * + */ +static int valid_ecryptfs_desc(const char *ecryptfs_desc) +{ + int i; + + if (strlen(ecryptfs_desc) != KEY_ECRYPTFS_DESC_LEN) { + pr_err("encrypted_key: key description must be %d hexadecimal " + "characters long\n", KEY_ECRYPTFS_DESC_LEN); + return -EINVAL; + } + + for (i = 0; i < KEY_ECRYPTFS_DESC_LEN; i++) { + if (!isxdigit(ecryptfs_desc[i])) { + pr_err("encrypted_key: key description must contain " + "only hexadecimal characters\n"); + return -EINVAL; + } + } + + return 0; +} + +/* + * valid_master_desc - verify the 'key-type:desc' of a new/updated master-key + * + * key-type:= "trusted:" | "user:" + * desc:= master-key description + * + * Verify that 'key-type' is valid and that 'desc' exists. On key update, + * only the master key description is permitted to change, not the key-type. + * The key-type remains constant. + * + * On success returns 0, otherwise -EINVAL. + */ +static int valid_master_desc(const char *new_desc, const char *orig_desc) +{ + if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN)) + goto out; + } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_USER_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN)) + goto out; + } else + goto out; + return 0; +out: + return -EINVAL; +} + +/* + * datablob_parse - parse the keyctl data + * + * datablob format: + * new [<format>] <master-key name> <decrypted data length> + * load [<format>] <master-key name> <decrypted data length> + * <encrypted iv + data> + * update <new-master-key name> + * + * Tokenizes a copy of the keyctl data, returning a pointer to each token, + * which is null terminated. + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, const char **format, + char **master_desc, char **decrypted_datalen, + char **hex_encoded_iv) +{ + substring_t args[MAX_OPT_ARGS]; + int ret = -EINVAL; + int key_cmd; + int key_format; + char *p, *keyword; + + keyword = strsep(&datablob, " \t"); + if (!keyword) { + pr_info("encrypted_key: insufficient parameters specified\n"); + return ret; + } + key_cmd = match_token(keyword, key_tokens, args); + + /* Get optional format: default | ecryptfs */ + p = strsep(&datablob, " \t"); + if (!p) { + pr_err("encrypted_key: insufficient parameters specified\n"); + return ret; + } + + key_format = match_token(p, key_format_tokens, args); + switch (key_format) { + case Opt_ecryptfs: + case Opt_default: + *format = p; + *master_desc = strsep(&datablob, " \t"); + break; + case Opt_error: + *master_desc = p; + break; + } + + if (!*master_desc) { + pr_info("encrypted_key: master key parameter is missing\n"); + goto out; + } + + if (valid_master_desc(*master_desc, NULL) < 0) { + pr_info("encrypted_key: master key parameter \'%s\' " + "is invalid\n", *master_desc); + goto out; + } + + if (decrypted_datalen) { + *decrypted_datalen = strsep(&datablob, " \t"); + if (!*decrypted_datalen) { + pr_info("encrypted_key: keylen parameter is missing\n"); + goto out; + } + } + + switch (key_cmd) { + case Opt_new: + if (!decrypted_datalen) { + pr_info("encrypted_key: keyword \'%s\' not allowed " + "when called from .update method\n", keyword); + break; + } + ret = 0; + break; + case Opt_load: + if (!decrypted_datalen) { + pr_info("encrypted_key: keyword \'%s\' not allowed " + "when called from .update method\n", keyword); + break; + } + *hex_encoded_iv = strsep(&datablob, " \t"); + if (!*hex_encoded_iv) { + pr_info("encrypted_key: hex blob is missing\n"); + break; + } + ret = 0; + break; + case Opt_update: + if (decrypted_datalen) { + pr_info("encrypted_key: keyword \'%s\' not allowed " + "when called from .instantiate method\n", + keyword); + break; + } + ret = 0; + break; + case Opt_err: + pr_info("encrypted_key: keyword \'%s\' not recognized\n", + keyword); + break; + } +out: + return ret; +} + +/* + * datablob_format - format as an ascii string, before copying to userspace + */ +static char *datablob_format(struct encrypted_key_payload *epayload, + size_t asciiblob_len) +{ + char *ascii_buf, *bufp; + u8 *iv = epayload->iv; + int len; + int i; + + ascii_buf = kmalloc(asciiblob_len + 1, GFP_KERNEL); + if (!ascii_buf) + goto out; + + ascii_buf[asciiblob_len] = '\0'; + + /* copy datablob master_desc and datalen strings */ + len = sprintf(ascii_buf, "%s %s %s ", epayload->format, + epayload->master_desc, epayload->datalen); + + /* convert the hex encoded iv, encrypted-data and HMAC to ascii */ + bufp = &ascii_buf[len]; + for (i = 0; i < (asciiblob_len - len) / 2; i++) + bufp = hex_byte_pack(bufp, iv[i]); +out: + return ascii_buf; +} + +/* + * request_user_key - request the user key + * + * Use a user provided key to encrypt/decrypt an encrypted-key. + */ +static struct key *request_user_key(const char *master_desc, u8 **master_key, + size_t *master_keylen) +{ + struct user_key_payload *upayload; + struct key *ukey; + + ukey = request_key(&key_type_user, master_desc, NULL); + if (IS_ERR(ukey)) + goto error; + + down_read(&ukey->sem); + upayload = ukey->payload.data; + *master_key = upayload->data; + *master_keylen = upayload->datalen; +error: + return ukey; +} + +static struct sdesc *alloc_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + sdesc->shash.flags = 0x0; + return sdesc; +} + +static int calc_hmac(u8 *digest, const u8 *key, unsigned int keylen, + const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hmacalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hmac_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_setkey(hmacalg, key, keylen); + if (!ret) + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +static int calc_hash(u8 *digest, const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +enum derived_key_type { ENC_KEY, AUTH_KEY }; + +/* Derive authentication/encryption key from trusted key */ +static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, + const u8 *master_key, size_t master_keylen) +{ + u8 *derived_buf; + unsigned int derived_buf_len; + int ret; + + derived_buf_len = strlen("AUTH_KEY") + 1 + master_keylen; + if (derived_buf_len < HASH_SIZE) + derived_buf_len = HASH_SIZE; + + derived_buf = kzalloc(derived_buf_len, GFP_KERNEL); + if (!derived_buf) { + pr_err("encrypted_key: out of memory\n"); + return -ENOMEM; + } + if (key_type) + strcpy(derived_buf, "AUTH_KEY"); + else + strcpy(derived_buf, "ENC_KEY"); + + memcpy(derived_buf + strlen(derived_buf) + 1, master_key, + master_keylen); + ret = calc_hash(derived_key, derived_buf, derived_buf_len); + kfree(derived_buf); + return ret; +} + +static int init_blkcipher_desc(struct blkcipher_desc *desc, const u8 *key, + unsigned int key_len, const u8 *iv, + unsigned int ivsize) +{ + int ret; + + desc->tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_err("encrypted_key: failed to load %s transform (%ld)\n", + blkcipher_alg, PTR_ERR(desc->tfm)); + return PTR_ERR(desc->tfm); + } + desc->flags = 0; + + ret = crypto_blkcipher_setkey(desc->tfm, key, key_len); + if (ret < 0) { + pr_err("encrypted_key: failed to setkey (%d)\n", ret); + crypto_free_blkcipher(desc->tfm); + return ret; + } + crypto_blkcipher_set_iv(desc->tfm, iv, ivsize); + return 0; +} + +static struct key *request_master_key(struct encrypted_key_payload *epayload, + u8 **master_key, size_t *master_keylen) +{ + struct key *mkey = NULL; + + if (!strncmp(epayload->master_desc, KEY_TRUSTED_PREFIX, + KEY_TRUSTED_PREFIX_LEN)) { + mkey = request_trusted_key(epayload->master_desc + + KEY_TRUSTED_PREFIX_LEN, + master_key, master_keylen); + } else if (!strncmp(epayload->master_desc, KEY_USER_PREFIX, + KEY_USER_PREFIX_LEN)) { + mkey = request_user_key(epayload->master_desc + + KEY_USER_PREFIX_LEN, + master_key, master_keylen); + } else + goto out; + + if (IS_ERR(mkey)) { + int ret = PTR_ERR(mkey); + + if (ret == -ENOTSUPP) + pr_info("encrypted_key: key %s not supported", + epayload->master_desc); + else + pr_info("encrypted_key: key %s not found", + epayload->master_desc); + goto out; + } + + dump_master_key(*master_key, *master_keylen); +out: + return mkey; +} + +/* Before returning data to userspace, encrypt decrypted data. */ +static int derived_key_encrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[2]; + struct scatterlist sg_out[1]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + unsigned int padlen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + padlen = encrypted_datalen - epayload->decrypted_datalen; + + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 2); + sg_set_buf(&sg_in[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_in[1], pad, padlen); + + sg_init_table(sg_out, 1); + sg_set_buf(sg_out, epayload->encrypted_data, encrypted_datalen); + + ret = crypto_blkcipher_encrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + pr_err("encrypted_key: failed to encrypt (%d)\n", ret); + else + dump_encrypted_data(epayload, encrypted_datalen); +out: + return ret; +} + +static int datablob_hmac_append(struct encrypted_key_payload *epayload, + const u8 *master_key, size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 *digest; + int ret; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + digest = epayload->format + epayload->datablob_len; + ret = calc_hmac(digest, derived_key, sizeof derived_key, + epayload->format, epayload->datablob_len); + if (!ret) + dump_hmac(NULL, digest, HASH_SIZE); +out: + return ret; +} + +/* verify HMAC before decrypting encrypted key */ +static int datablob_hmac_verify(struct encrypted_key_payload *epayload, + const u8 *format, const u8 *master_key, + size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 digest[HASH_SIZE]; + int ret; + char *p; + unsigned short len; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + len = epayload->datablob_len; + if (!format) { + p = epayload->master_desc; + len -= strlen(epayload->format) + 1; + } else + p = epayload->format; + + ret = calc_hmac(digest, derived_key, sizeof derived_key, p, len); + if (ret < 0) + goto out; + ret = memcmp(digest, epayload->format + epayload->datablob_len, + sizeof digest); + if (ret) { + ret = -EINVAL; + dump_hmac("datablob", + epayload->format + epayload->datablob_len, + HASH_SIZE); + dump_hmac("calc", digest, HASH_SIZE); + } +out: + return ret; +} + +static int derived_key_decrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[1]; + struct scatterlist sg_out[2]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_encrypted_data(epayload, encrypted_datalen); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 1); + sg_init_table(sg_out, 2); + sg_set_buf(sg_in, epayload->encrypted_data, encrypted_datalen); + sg_set_buf(&sg_out[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_out[1], pad, sizeof pad); + + ret = crypto_blkcipher_decrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); +out: + return ret; +} + +/* Allocate memory for decrypted key and datablob. */ +static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, + const char *format, + const char *master_desc, + const char *datalen) +{ + struct encrypted_key_payload *epayload = NULL; + unsigned short datablob_len; + unsigned short decrypted_datalen; + unsigned short payload_datalen; + unsigned int encrypted_datalen; + unsigned int format_len; + long dlen; + int ret; + + ret = strict_strtol(datalen, 10, &dlen); + if (ret < 0 || dlen < MIN_DATA_SIZE || dlen > MAX_DATA_SIZE) + return ERR_PTR(-EINVAL); + + format_len = (!format) ? strlen(key_format_default) : strlen(format); + decrypted_datalen = dlen; + payload_datalen = decrypted_datalen; + if (format && !strcmp(format, key_format_ecryptfs)) { + if (dlen != ECRYPTFS_MAX_KEY_BYTES) { + pr_err("encrypted_key: keylen for the ecryptfs format " + "must be equal to %d bytes\n", + ECRYPTFS_MAX_KEY_BYTES); + return ERR_PTR(-EINVAL); + } + decrypted_datalen = ECRYPTFS_MAX_KEY_BYTES; + payload_datalen = sizeof(struct ecryptfs_auth_tok); + } + + encrypted_datalen = roundup(decrypted_datalen, blksize); + + datablob_len = format_len + 1 + strlen(master_desc) + 1 + + strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen; + + ret = key_payload_reserve(key, payload_datalen + datablob_len + + HASH_SIZE + 1); + if (ret < 0) + return ERR_PTR(ret); + + epayload = kzalloc(sizeof(*epayload) + payload_datalen + + datablob_len + HASH_SIZE + 1, GFP_KERNEL); + if (!epayload) + return ERR_PTR(-ENOMEM); + + epayload->payload_datalen = payload_datalen; + epayload->decrypted_datalen = decrypted_datalen; + epayload->datablob_len = datablob_len; + return epayload; +} + +static int encrypted_key_decrypt(struct encrypted_key_payload *epayload, + const char *format, const char *hex_encoded_iv) +{ + struct key *mkey; + u8 derived_key[HASH_SIZE]; + u8 *master_key; + u8 *hmac; + const char *hex_encoded_data; + unsigned int encrypted_datalen; + size_t master_keylen; + size_t asciilen; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + asciilen = (ivsize + 1 + encrypted_datalen + HASH_SIZE) * 2; + if (strlen(hex_encoded_iv) != asciilen) + return -EINVAL; + + hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; + ret = hex2bin(epayload->iv, hex_encoded_iv, ivsize); + if (ret < 0) + return -EINVAL; + ret = hex2bin(epayload->encrypted_data, hex_encoded_data, + encrypted_datalen); + if (ret < 0) + return -EINVAL; + + hmac = epayload->format + epayload->datablob_len; + ret = hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), + HASH_SIZE); + if (ret < 0) + return -EINVAL; + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = datablob_hmac_verify(epayload, format, master_key, master_keylen); + if (ret < 0) { + pr_err("encrypted_key: bad hmac (%d)\n", ret); + goto out; + } + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_decrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + pr_err("encrypted_key: failed to decrypt key (%d)\n", ret); +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +static void __ekey_init(struct encrypted_key_payload *epayload, + const char *format, const char *master_desc, + const char *datalen) +{ + unsigned int format_len; + + format_len = (!format) ? strlen(key_format_default) : strlen(format); + epayload->format = epayload->payload_data + epayload->payload_datalen; + epayload->master_desc = epayload->format + format_len + 1; + epayload->datalen = epayload->master_desc + strlen(master_desc) + 1; + epayload->iv = epayload->datalen + strlen(datalen) + 1; + epayload->encrypted_data = epayload->iv + ivsize + 1; + epayload->decrypted_data = epayload->payload_data; + + if (!format) + memcpy(epayload->format, key_format_default, format_len); + else { + if (!strcmp(format, key_format_ecryptfs)) + epayload->decrypted_data = + ecryptfs_get_auth_tok_key((struct ecryptfs_auth_tok *)epayload->payload_data); + + memcpy(epayload->format, format, format_len); + } + + memcpy(epayload->master_desc, master_desc, strlen(master_desc)); + memcpy(epayload->datalen, datalen, strlen(datalen)); +} + +/* + * encrypted_init - initialize an encrypted key + * + * For a new key, use a random number for both the iv and data + * itself. For an old key, decrypt the hex encoded data. + */ +static int encrypted_init(struct encrypted_key_payload *epayload, + const char *key_desc, const char *format, + const char *master_desc, const char *datalen, + const char *hex_encoded_iv) +{ + int ret = 0; + + if (format && !strcmp(format, key_format_ecryptfs)) { + ret = valid_ecryptfs_desc(key_desc); + if (ret < 0) + return ret; + + ecryptfs_fill_auth_tok((struct ecryptfs_auth_tok *)epayload->payload_data, + key_desc); + } + + __ekey_init(epayload, format, master_desc, datalen); + if (!hex_encoded_iv) { + get_random_bytes(epayload->iv, ivsize); + + get_random_bytes(epayload->decrypted_data, + epayload->decrypted_datalen); + } else + ret = encrypted_key_decrypt(epayload, format, hex_encoded_iv); + return ret; +} + +/* + * encrypted_instantiate - instantiate an encrypted key + * + * Decrypt an existing encrypted datablob or create a new encrypted key + * based on a kernel random number. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_instantiate(struct key *key, const void *data, + size_t datalen) +{ + struct encrypted_key_payload *epayload = NULL; + char *datablob = NULL; + const char *format = NULL; + char *master_desc = NULL; + char *decrypted_datalen = NULL; + char *hex_encoded_iv = NULL; + int ret; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + datablob[datalen] = 0; + memcpy(datablob, data, datalen); + ret = datablob_parse(datablob, &format, &master_desc, + &decrypted_datalen, &hex_encoded_iv); + if (ret < 0) + goto out; + + epayload = encrypted_key_alloc(key, format, master_desc, + decrypted_datalen); + if (IS_ERR(epayload)) { + ret = PTR_ERR(epayload); + goto out; + } + ret = encrypted_init(epayload, key->description, format, master_desc, + decrypted_datalen, hex_encoded_iv); + if (ret < 0) { + kfree(epayload); + goto out; + } + + rcu_assign_keypointer(key, epayload); +out: + kfree(datablob); + return ret; +} + +static void encrypted_rcu_free(struct rcu_head *rcu) +{ + struct encrypted_key_payload *epayload; + + epayload = container_of(rcu, struct encrypted_key_payload, rcu); + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(epayload); +} + +/* + * encrypted_update - update the master key description + * + * Change the master key description for an existing encrypted key. + * The next read will return an encrypted datablob using the new + * master key description. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_update(struct key *key, const void *data, size_t datalen) +{ + struct encrypted_key_payload *epayload = key->payload.data; + struct encrypted_key_payload *new_epayload; + char *buf; + char *new_master_desc = NULL; + const char *format = NULL; + int ret = 0; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + buf = kmalloc(datalen + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[datalen] = 0; + memcpy(buf, data, datalen); + ret = datablob_parse(buf, &format, &new_master_desc, NULL, NULL); + if (ret < 0) + goto out; + + ret = valid_master_desc(new_master_desc, epayload->master_desc); + if (ret < 0) + goto out; + + new_epayload = encrypted_key_alloc(key, epayload->format, + new_master_desc, epayload->datalen); + if (IS_ERR(new_epayload)) { + ret = PTR_ERR(new_epayload); + goto out; + } + + __ekey_init(new_epayload, epayload->format, new_master_desc, + epayload->datalen); + + memcpy(new_epayload->iv, epayload->iv, ivsize); + memcpy(new_epayload->payload_data, epayload->payload_data, + epayload->payload_datalen); + + rcu_assign_keypointer(key, new_epayload); + call_rcu(&epayload->rcu, encrypted_rcu_free); +out: + kfree(buf); + return ret; +} + +/* + * encrypted_read - format and copy the encrypted data to userspace + * + * The resulting datablob format is: + * <master-key name> <decrypted data length> <encrypted iv> <encrypted data> + * + * On success, return to userspace the encrypted key datablob size. + */ +static long encrypted_read(const struct key *key, char __user *buffer, + size_t buflen) +{ + struct encrypted_key_payload *epayload; + struct key *mkey; + u8 *master_key; + size_t master_keylen; + char derived_key[HASH_SIZE]; + char *ascii_buf; + size_t asciiblob_len; + int ret; + + epayload = rcu_dereference_key(key); + + /* returns the hex encoded iv, encrypted-data, and hmac as ascii */ + asciiblob_len = epayload->datablob_len + ivsize + 1 + + roundup(epayload->decrypted_datalen, blksize) + + (HASH_SIZE * 2); + + if (!buffer || buflen < asciiblob_len) + return asciiblob_len; + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_encrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + goto out; + + ret = datablob_hmac_append(epayload, master_key, master_keylen); + if (ret < 0) + goto out; + + ascii_buf = datablob_format(epayload, asciiblob_len); + if (!ascii_buf) { + ret = -ENOMEM; + goto out; + } + + up_read(&mkey->sem); + key_put(mkey); + + if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0) + ret = -EFAULT; + kfree(ascii_buf); + + return asciiblob_len; +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +/* + * encrypted_destroy - before freeing the key, clear the decrypted data + * + * Before freeing the key, clear the memory containing the decrypted + * key data. + */ +static void encrypted_destroy(struct key *key) +{ + struct encrypted_key_payload *epayload = key->payload.data; + + if (!epayload) + return; + + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(key->payload.data); +} + +struct key_type key_type_encrypted = { + .name = "encrypted", + .instantiate = encrypted_instantiate, + .update = encrypted_update, + .match = user_match, + .destroy = encrypted_destroy, + .describe = user_describe, + .read = encrypted_read, +}; +EXPORT_SYMBOL_GPL(key_type_encrypted); + +static void encrypted_shash_release(void) +{ + if (hashalg) + crypto_free_shash(hashalg); + if (hmacalg) + crypto_free_shash(hmacalg); +} + +static int __init encrypted_shash_alloc(void) +{ + int ret; + + hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmacalg)) { + pr_info("encrypted_key: could not allocate crypto %s\n", + hmac_alg); + return PTR_ERR(hmacalg); + } + + hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hashalg)) { + pr_info("encrypted_key: could not allocate crypto %s\n", + hash_alg); + ret = PTR_ERR(hashalg); + goto hashalg_fail; + } + + return 0; + +hashalg_fail: + crypto_free_shash(hmacalg); + return ret; +} + +static int __init init_encrypted(void) +{ + int ret; + + ret = encrypted_shash_alloc(); + if (ret < 0) + return ret; + ret = register_key_type(&key_type_encrypted); + if (ret < 0) + goto out; + return aes_get_sizes(); +out: + encrypted_shash_release(); + return ret; + +} + +static void __exit cleanup_encrypted(void) +{ + encrypted_shash_release(); + unregister_key_type(&key_type_encrypted); +} + +late_initcall(init_encrypted); +module_exit(cleanup_encrypted); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/encrypted-keys/encrypted.h b/security/keys/encrypted-keys/encrypted.h new file mode 100644 index 00000000..8136a2d4 --- /dev/null +++ b/security/keys/encrypted-keys/encrypted.h @@ -0,0 +1,66 @@ +#ifndef __ENCRYPTED_KEY_H +#define __ENCRYPTED_KEY_H + +#define ENCRYPTED_DEBUG 0 +#if defined(CONFIG_TRUSTED_KEYS) || \ + (defined(CONFIG_TRUSTED_KEYS_MODULE) && defined(CONFIG_ENCRYPTED_KEYS_MODULE)) +extern struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen); +#else +static inline struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, + size_t *master_keylen) +{ + return ERR_PTR(-EOPNOTSUPP); +} +#endif + +#if ENCRYPTED_DEBUG +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ + print_hex_dump(KERN_ERR, "master key: ", DUMP_PREFIX_NONE, 32, 1, + master_key, master_keylen, 0); +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ + print_hex_dump(KERN_ERR, "decrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->decrypted_data, + epayload->decrypted_datalen, 0); +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ + print_hex_dump(KERN_ERR, "encrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->encrypted_data, encrypted_datalen, 0); +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ + if (str) + pr_info("encrypted_key: %s", str); + print_hex_dump(KERN_ERR, "hmac: ", DUMP_PREFIX_NONE, 32, 1, digest, + hmac_size, 0); +} +#else +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ +} +#endif +#endif diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c new file mode 100644 index 00000000..013f7e5d --- /dev/null +++ b/security/keys/encrypted-keys/masterkey_trusted.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (C) 2010 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Roberto Sassu <roberto.sassu@polito.it> + * + * 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, version 2 of the License. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/err.h> +#include <keys/trusted-type.h> +#include <keys/encrypted-type.h> +#include "encrypted.h" + +/* + * request_trusted_key - request the trusted key + * + * Trusted keys are sealed to PCRs and other metadata. Although userspace + * manages both trusted/encrypted key-types, like the encrypted key type + * data, trusted key type data is not visible decrypted from userspace. + */ +struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen) +{ + struct trusted_key_payload *tpayload; + struct key *tkey; + + tkey = request_key(&key_type_trusted, trusted_desc, NULL); + if (IS_ERR(tkey)) + goto error; + + down_read(&tkey->sem); + tpayload = tkey->payload.data; + *master_key = tpayload->key; + *master_keylen = tpayload->key_len; +error: + return tkey; +} diff --git a/security/keys/gc.c b/security/keys/gc.c new file mode 100644 index 00000000..a42b4553 --- /dev/null +++ b/security/keys/gc.c @@ -0,0 +1,390 @@ +/* Key garbage collector + * + * Copyright (C) 2009-2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <keys/keyring-type.h> +#include "internal.h" + +/* + * Delay between key revocation/expiry in seconds + */ +unsigned key_gc_delay = 5 * 60; + +/* + * Reaper for unused keys. + */ +static void key_garbage_collector(struct work_struct *work); +DECLARE_WORK(key_gc_work, key_garbage_collector); + +/* + * Reaper for links from keyrings to dead keys. + */ +static void key_gc_timer_func(unsigned long); +static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); + +static time_t key_gc_next_run = LONG_MAX; +static struct key_type *key_gc_dead_keytype; + +static unsigned long key_gc_flags; +#define KEY_GC_KEY_EXPIRED 0 /* A key expired and needs unlinking */ +#define KEY_GC_REAP_KEYTYPE 1 /* A keytype is being unregistered */ +#define KEY_GC_REAPING_KEYTYPE 2 /* Cleared when keytype reaped */ + + +/* + * Any key whose type gets unregistered will be re-typed to this if it can't be + * immediately unlinked. + */ +struct key_type key_type_dead = { + .name = "dead", +}; + +/* + * Schedule a garbage collection run. + * - time precision isn't particularly important + */ +void key_schedule_gc(time_t gc_at) +{ + unsigned long expires; + time_t now = current_kernel_time().tv_sec; + + kenter("%ld", gc_at - now); + + if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) { + kdebug("IMMEDIATE"); + queue_work(system_nrt_wq, &key_gc_work); + } else if (gc_at < key_gc_next_run) { + kdebug("DEFERRED"); + key_gc_next_run = gc_at; + expires = jiffies + (gc_at - now) * HZ; + mod_timer(&key_gc_timer, expires); + } +} + +/* + * Some key's cleanup time was met after it expired, so we need to get the + * reaper to go through a cycle finding expired keys. + */ +static void key_gc_timer_func(unsigned long data) +{ + kenter(""); + key_gc_next_run = LONG_MAX; + set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); + queue_work(system_nrt_wq, &key_gc_work); +} + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_gc_wait_bit(void *flags) +{ + schedule(); + return 0; +} + +/* + * Reap keys of dead type. + * + * We use three flags to make sure we see three complete cycles of the garbage + * collector: the first to mark keys of that type as being dead, the second to + * collect dead links and the third to clean up the dead keys. We have to be + * careful as there may already be a cycle in progress. + * + * The caller must be holding key_types_sem. + */ +void key_gc_keytype(struct key_type *ktype) +{ + kenter("%s", ktype->name); + + key_gc_dead_keytype = ktype; + set_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); + smp_mb(); + set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags); + + kdebug("schedule"); + queue_work(system_nrt_wq, &key_gc_work); + + kdebug("sleep"); + wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE, key_gc_wait_bit, + TASK_UNINTERRUPTIBLE); + + key_gc_dead_keytype = NULL; + kleave(""); +} + +/* + * Garbage collect pointers from a keyring. + * + * Not called with any locks held. The keyring's key struct will not be + * deallocated under us as only our caller may deallocate it. + */ +static void key_gc_keyring(struct key *keyring, time_t limit) +{ + struct keyring_list *klist; + struct key *key; + int loop; + + kenter("%x", key_serial(keyring)); + + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto dont_gc; + + /* scan the keyring looking for dead keys */ + rcu_read_lock(); + klist = rcu_dereference(keyring->payload.subscriptions); + if (!klist) + goto unlock_dont_gc; + + loop = klist->nkeys; + smp_rmb(); + for (loop--; loop >= 0; loop--) { + key = klist->keys[loop]; + if (test_bit(KEY_FLAG_DEAD, &key->flags) || + (key->expiry > 0 && key->expiry <= limit)) + goto do_gc; + } + +unlock_dont_gc: + rcu_read_unlock(); +dont_gc: + kleave(" [no gc]"); + return; + +do_gc: + rcu_read_unlock(); + + keyring_gc(keyring, limit); + kleave(" [gc]"); +} + +/* + * Garbage collect an unreferenced, detached key + */ +static noinline void key_gc_unused_key(struct key *key) +{ + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); +} + +/* + * Garbage collector for unused keys. + * + * This is done in process context so that we don't have to disable interrupts + * all over the place. key_put() schedules this rather than trying to do the + * cleanup itself, which means key_put() doesn't have to sleep. + */ +static void key_garbage_collector(struct work_struct *work) +{ + static u8 gc_state; /* Internal persistent state */ +#define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */ +#define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */ +#define KEY_GC_SET_TIMER 0x04 /* - We need to restart the timer */ +#define KEY_GC_REAPING_DEAD_1 0x10 /* - We need to mark dead keys */ +#define KEY_GC_REAPING_DEAD_2 0x20 /* - We need to reap dead key links */ +#define KEY_GC_REAPING_DEAD_3 0x40 /* - We need to reap dead keys */ +#define KEY_GC_FOUND_DEAD_KEY 0x80 /* - We found at least one dead key */ + + struct rb_node *cursor; + struct key *key; + time_t new_timer, limit; + + kenter("[%lx,%x]", key_gc_flags, gc_state); + + limit = current_kernel_time().tv_sec; + if (limit > key_gc_delay) + limit -= key_gc_delay; + else + limit = key_gc_delay; + + /* Work out what we're going to be doing in this pass */ + gc_state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2; + gc_state <<= 1; + if (test_and_clear_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags)) + gc_state |= KEY_GC_REAPING_LINKS | KEY_GC_SET_TIMER; + + if (test_and_clear_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) + gc_state |= KEY_GC_REAPING_DEAD_1; + kdebug("new pass %x", gc_state); + + new_timer = LONG_MAX; + + /* As only this function is permitted to remove things from the key + * serial tree, if cursor is non-NULL then it will always point to a + * valid node in the tree - even if lock got dropped. + */ + spin_lock(&key_serial_lock); + cursor = rb_first(&key_serial_tree); + +continue_scanning: + while (cursor) { + key = rb_entry(cursor, struct key, serial_node); + cursor = rb_next(cursor); + + if (atomic_read(&key->usage) == 0) + goto found_unreferenced_key; + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) { + if (key->type == key_gc_dead_keytype) { + gc_state |= KEY_GC_FOUND_DEAD_KEY; + set_bit(KEY_FLAG_DEAD, &key->flags); + key->perm = 0; + goto skip_dead_key; + } + } + + if (gc_state & KEY_GC_SET_TIMER) { + if (key->expiry > limit && key->expiry < new_timer) { + kdebug("will expire %x in %ld", + key_serial(key), key->expiry - limit); + new_timer = key->expiry; + } + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) + if (key->type == key_gc_dead_keytype) + gc_state |= KEY_GC_FOUND_DEAD_KEY; + + if ((gc_state & KEY_GC_REAPING_LINKS) || + unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { + if (key->type == &key_type_keyring) + goto found_keyring; + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) + if (key->type == key_gc_dead_keytype) + goto destroy_dead_key; + + skip_dead_key: + if (spin_is_contended(&key_serial_lock) || need_resched()) + goto contended; + } + +contended: + spin_unlock(&key_serial_lock); + +maybe_resched: + if (cursor) { + cond_resched(); + spin_lock(&key_serial_lock); + goto continue_scanning; + } + + /* We've completed the pass. Set the timer if we need to and queue a + * new cycle if necessary. We keep executing cycles until we find one + * where we didn't reap any keys. + */ + kdebug("pass complete"); + + if (gc_state & KEY_GC_SET_TIMER && new_timer != (time_t)LONG_MAX) { + new_timer += key_gc_delay; + key_schedule_gc(new_timer); + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { + /* Make sure everyone revalidates their keys if we marked a + * bunch as being dead and make sure all keyring ex-payloads + * are destroyed. + */ + kdebug("dead sync"); + synchronize_rcu(); + } + + if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | + KEY_GC_REAPING_DEAD_2))) { + if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { + /* No remaining dead keys: short circuit the remaining + * keytype reap cycles. + */ + kdebug("dead short"); + gc_state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2); + gc_state |= KEY_GC_REAPING_DEAD_3; + } else { + gc_state |= KEY_GC_REAP_AGAIN; + } + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { + kdebug("dead wake"); + smp_mb(); + clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); + wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + } + + if (gc_state & KEY_GC_REAP_AGAIN) + queue_work(system_nrt_wq, &key_gc_work); + kleave(" [end %x]", gc_state); + return; + + /* We found an unreferenced key - once we've removed it from the tree, + * we can safely drop the lock. + */ +found_unreferenced_key: + kdebug("unrefd key %d", key->serial); + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + key_gc_unused_key(key); + gc_state |= KEY_GC_REAP_AGAIN; + 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 + * can reap the keys to which it refers. + */ +found_keyring: + spin_unlock(&key_serial_lock); + kdebug("scan keyring %d", key->serial); + key_gc_keyring(key, limit); + goto maybe_resched; + + /* We found a dead key that is still referenced. Reset its type and + * destroy its payload with its semaphore held. + */ +destroy_dead_key: + spin_unlock(&key_serial_lock); + kdebug("destroy key %d", key->serial); + down_write(&key->sem); + key->type = &key_type_dead; + if (key_gc_dead_keytype->destroy) + key_gc_dead_keytype->destroy(key); + memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); + up_write(&key->sem); + goto maybe_resched; +} diff --git a/security/keys/internal.h b/security/keys/internal.h new file mode 100644 index 00000000..65647f82 --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,251 @@ +/* Authentication token and access key management internal defs + * + * Copyright (C) 2003-5, 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include <linux/sched.h> +#include <linux/key-type.h> + +#ifdef __KDEBUG +#define kenter(FMT, ...) \ + printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + printk(KERN_DEBUG " "FMT"\n", ##__VA_ARGS__) +#else +#define kenter(FMT, ...) \ + no_printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + no_printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__) +#endif + +extern struct key_type key_type_dead; +extern struct key_type key_type_user; +extern struct key_type key_type_logon; + +/*****************************************************************************/ +/* + * Keep track of keys for a user. + * + * This needs to be separate to user_struct to avoid a refcount-loop + * (user_struct pins some keyrings which pin this struct). + * + * We also keep track of keys under request from userspace for this UID here. + */ +struct key_user { + struct rb_node node; + struct mutex cons_lock; /* construction initiation lock */ + spinlock_t lock; + atomic_t usage; /* for accessing qnkeys & qnbytes */ + atomic_t nkeys; /* number of keys */ + atomic_t nikeys; /* number of instantiated keys */ + uid_t uid; + struct user_namespace *user_ns; + int qnkeys; /* number of keys allocated to this user */ + int qnbytes; /* number of bytes allocated to this user */ +}; + +extern struct rb_root key_user_tree; +extern spinlock_t key_user_lock; +extern struct key_user root_key_user; + +extern struct key_user *key_user_lookup(uid_t uid, + struct user_namespace *user_ns); +extern void key_user_put(struct key_user *user); + +/* + * Key quota limits. + * - root has its own separate limits to everyone else + */ +extern unsigned key_quota_root_maxkeys; +extern unsigned key_quota_root_maxbytes; +extern unsigned key_quota_maxkeys; +extern unsigned key_quota_maxbytes; + +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + + +extern struct kmem_cache *key_jar; +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct mutex key_construction_mutex; +extern wait_queue_head_t request_key_conswq; + + +extern struct key_type *key_type_lookup(const char *type); +extern void key_type_put(struct key_type *ktype); + +extern int __key_link_begin(struct key *keyring, + const struct key_type *type, + const char *description, + unsigned long *_prealloc); +extern int __key_link_check_live_key(struct key *keyring, struct key *key); +extern void __key_link(struct key *keyring, struct key *key, + unsigned long *_prealloc); +extern void __key_link_end(struct key *keyring, + struct key_type *type, + unsigned long prealloc); + +extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *type, + const char *description, + key_perm_t perm); + +extern struct key *keyring_search_instkey(struct key *keyring, + key_serial_t target_id); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern key_ref_t keyring_search_aux(key_ref_t keyring_ref, + const struct cred *cred, + struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check); + +extern key_ref_t search_my_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check, + const struct cred *cred); +extern key_ref_t search_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + const struct cred *cred); + +extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); + +extern int install_user_keyrings(void); +extern int install_thread_keyring_to_cred(struct cred *); +extern int install_process_keyring_to_cred(struct cred *); +extern int install_session_keyring_to_cred(struct cred *, struct key *); + +extern struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags); + +extern int lookup_user_key_possessed(const struct key *key, const void *target); +extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, + key_perm_t perm); +#define KEY_LOOKUP_CREATE 0x01 +#define KEY_LOOKUP_PARTIAL 0x02 +#define KEY_LOOKUP_FOR_UNLINK 0x04 + +extern long join_session_keyring(const char *name); + +extern struct work_struct key_gc_work; +extern unsigned key_gc_delay; +extern void keyring_gc(struct key *keyring, time_t limit); +extern void key_schedule_gc(time_t expiry_at); +extern void key_gc_keytype(struct key_type *ktype); + +extern int key_task_permission(const key_ref_t key_ref, + const struct cred *cred, + key_perm_t perm); + +/* + * Check to see whether permission is granted to use a key in the desired way. + */ +static inline int key_permission(const key_ref_t key_ref, key_perm_t perm) +{ + return key_task_permission(key_ref, current_cred(), perm); +} + +/* required permissions */ +#define KEY_VIEW 0x01 /* require permission to view attributes */ +#define KEY_READ 0x02 /* require permission to read content */ +#define KEY_WRITE 0x04 /* require permission to update / modify */ +#define KEY_SEARCH 0x08 /* require permission to search (keyring) or find (key) */ +#define KEY_LINK 0x10 /* require permission to link */ +#define KEY_SETATTR 0x20 /* require permission to change attributes */ +#define KEY_ALL 0x3f /* all the above permissions */ + +/* + * Authorisation record for request_key(). + */ +struct request_key_auth { + struct key *target_key; + struct key *dest_keyring; + const struct cred *cred; + void *callout_info; + size_t callout_len; + pid_t pid; +}; + +extern struct key_type key_type_request_key_auth; +extern struct key *request_key_auth_new(struct key *target, + const void *callout_info, + size_t callout_len, + struct key *dest_keyring); + +extern struct key *key_get_instantiation_authkey(key_serial_t target_id); + +/* + * keyctl() functions + */ +extern long keyctl_get_keyring_ID(key_serial_t, int); +extern long keyctl_join_session_keyring(const char __user *); +extern long keyctl_update_key(key_serial_t, const void __user *, size_t); +extern long keyctl_revoke_key(key_serial_t); +extern long keyctl_keyring_clear(key_serial_t); +extern long keyctl_keyring_link(key_serial_t, key_serial_t); +extern long keyctl_keyring_unlink(key_serial_t, key_serial_t); +extern long keyctl_describe_key(key_serial_t, char __user *, size_t); +extern long keyctl_keyring_search(key_serial_t, const char __user *, + const char __user *, key_serial_t); +extern long keyctl_read_key(key_serial_t, char __user *, size_t); +extern long keyctl_chown_key(key_serial_t, uid_t, gid_t); +extern long keyctl_setperm_key(key_serial_t, key_perm_t); +extern long keyctl_instantiate_key(key_serial_t, const void __user *, + size_t, key_serial_t); +extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); +extern long keyctl_set_reqkey_keyring(int); +extern long keyctl_set_timeout(key_serial_t, unsigned); +extern long keyctl_assume_authority(key_serial_t); +extern long keyctl_get_security(key_serial_t keyid, char __user *buffer, + size_t buflen); +extern long keyctl_session_to_parent(void); +extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t); +extern long keyctl_instantiate_key_iov(key_serial_t, + const struct iovec __user *, + unsigned, key_serial_t); + +extern long keyctl_instantiate_key_common(key_serial_t, + const struct iovec __user *, + unsigned, size_t, key_serial_t); + +/* + * Debugging key validation + */ +#ifdef KEY_DEBUGGING +extern void __key_check(const struct key *); + +static inline void key_check(const struct key *key) +{ + if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC)) + __key_check(key); +} + +#else + +#define key_check(key) do {} while(0) + +#endif + +#endif /* _INTERNAL_H */ diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 00000000..06783cff --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1031 @@ +/* Basic authentication token and access key management + * + * Copyright (C) 2004-2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/poison.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/workqueue.h> +#include <linux/random.h> +#include <linux/err.h> +#include <linux/user_namespace.h> +#include "internal.h" + +struct kmem_cache *key_jar; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +DEFINE_SPINLOCK(key_serial_lock); + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +DEFINE_SPINLOCK(key_user_lock); + +unsigned int key_quota_root_maxkeys = 200; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 20000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +/* We serialise key instantiation and link */ +DEFINE_MUTEX(key_construction_mutex); + +#ifdef KEY_DEBUGGING +void __key_check(const struct key *key) +{ + printk("__key_check: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} +#endif + +/* + * Get the key quota record for a user, allocating a new record if one doesn't + * already exist. + */ +struct key_user *key_user_lookup(uid_t uid, struct user_namespace *user_ns) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent = NULL; + struct rb_node **p; + +try_again: + p = &key_user_tree.rb_node; + spin_lock(&key_user_lock); + + /* search the tree for a user record with a matching UID */ + while (*p) { + parent = *p; + user = rb_entry(parent, struct key_user, node); + + if (uid < user->uid) + p = &(*p)->rb_left; + else if (uid > user->uid) + p = &(*p)->rb_right; + else if (user_ns < user->user_ns) + p = &(*p)->rb_left; + else if (user_ns > user->user_ns) + p = &(*p)->rb_right; + else + goto found; + } + + /* if we get here, we failed to find a match in the tree */ + if (!candidate) { + /* allocate a candidate user record if we don't already have + * one */ + spin_unlock(&key_user_lock); + + user = NULL; + candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + if (unlikely(!candidate)) + goto out; + + /* the allocation may have scheduled, so we need to repeat the + * search lest someone else added the record whilst we were + * asleep */ + goto try_again; + } + + /* if we get here, then the user record still hadn't appeared on the + * second pass - so we use the candidate record */ + atomic_set(&candidate->usage, 1); + atomic_set(&candidate->nkeys, 0); + atomic_set(&candidate->nikeys, 0); + candidate->uid = uid; + candidate->user_ns = get_user_ns(user_ns); + candidate->qnkeys = 0; + candidate->qnbytes = 0; + spin_lock_init(&candidate->lock); + mutex_init(&candidate->cons_lock); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ +found: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + kfree(candidate); +out: + return user; +} + +/* + * Dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + put_user_ns(user->user_ns); + + kfree(user); + } +} + +/* + * Allocate a serial number for a key. These are assigned randomly to avoid + * security issues through covert channel problems. + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + /* propose a random serial number and look for a hole for it in the + * serial number tree */ + do { + get_random_bytes(&key->serial, sizeof(key->serial)); + + key->serial >>= 1; /* negative numbers are not permitted */ + } while (key->serial < 3); + + spin_lock(&key_serial_lock); + +attempt_insertion: + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + return; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ +serial_exists: + for (;;) { + key->serial++; + if (key->serial < 3) { + key->serial = 3; + goto attempt_insertion; + } + + parent = rb_next(parent); + if (!parent) + goto attempt_insertion; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto attempt_insertion; + } +} + +/** + * key_alloc - Allocate a key of the specified type. + * @type: The type of key to allocate. + * @desc: The key description to allow the key to be searched out. + * @uid: The owner of the new key. + * @gid: The group ID for the new key's group permissions. + * @cred: The credentials specifying UID namespace. + * @perm: The permissions mask of the new key. + * @flags: Flags specifying quota properties. + * + * 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 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 + * quota is available, -EDQUOT will be returned. + * + * The LSM security modules can prevent a key being created, in which case + * -EACCES will be returned. + * + * Returns a pointer to the new key if successful and an error code otherwise. + * + * Note that the caller needs to ensure the key type isn't uninstantiated. + * Internally this can be done by locking key_types_sem. Externally, this can + * be done by either never unregistering the key type, or making sure + * key_alloc() calls don't race with module unloading. + */ +struct key *key_alloc(struct key_type *type, const char *desc, + uid_t uid, gid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + int ret; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + if (type->vet_description) { + ret = type->vet_description(desc); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + desclen = strlen(desc) + 1; + quotalen = desclen + type->def_datalen; + + /* get hold of the key tracking for this user */ + user = key_user_lookup(uid, cred->user->user_ns); + if (!user) + goto no_memory_1; + + /* check that the user's quota permits allocation of another key and + * its description */ + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&user->lock); + if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { + if (user->qnkeys + 1 >= maxkeys || + user->qnbytes + quotalen >= maxbytes || + user->qnbytes + quotalen < user->qnbytes) + goto no_quota; + } + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, GFP_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmemdup(desc, desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + } + + atomic_set(&key->usage, 1); + init_rwsem(&key->sem); + lockdep_set_class(&key->sem, &type->lock_class); + key->type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->flags = 0; + key->expiry = 0; + key->payload.data = NULL; + key->security = NULL; + + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) + key->flags |= 1 << KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* let the security module know about the key */ + ret = security_key_alloc(key, cred, flags); + if (ret < 0) + goto security_error; + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + +error: + return key; + +security_error: + kfree(key->description); + kmem_cache_free(key_jar, key); + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + key = ERR_PTR(ret); + goto error; + +no_memory_3: + kmem_cache_free(key_jar, key); +no_memory_2: + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); +no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + +no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; +} +EXPORT_SYMBOL(key_alloc); + +/** + * key_payload_reserve - Adjust data quota reservation for the key's payload + * @key: The key to make the reservation for. + * @datalen: The amount of data payload the caller now wants. + * + * Adjust the amount of the owning user's key data quota that a key reserves. + * If the amount is increased, then -EDQUOT may be returned if there isn't + * enough free quota available. + * + * If successful, 0 is returned. + */ +int key_payload_reserve(struct key *key, size_t datalen) +{ + int delta = (int)datalen - key->datalen; + int ret = 0; + + key_check(key); + + /* contemplate the quota adjustment */ + if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = (key->user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&key->user->lock); + + if (delta > 0 && + (key->user->qnbytes + delta >= maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; +} +EXPORT_SYMBOL(key_payload_reserve); + +/* + * Instantiate a key and link it into the target keyring atomically. Must be + * called with the target keyring's semaphore writelocked. The target key's + * semaphore need not be locked as instantiation is serialised by + * key_construction_mutex. + */ +static int __key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring, + struct key *authkey, + unsigned long *_prealloc) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + /* and link it into the destination keyring */ + if (keyring) + __key_link(keyring, key, _prealloc); + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + } + } + + mutex_unlock(&key_construction_mutex); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret; +} + +/** + * key_instantiate_and_link - Instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @data: The data to use to instantiate the keyring. + * @datalen: The length of @data. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Instantiate a key that's in the uninstantiated state using the provided data + * and, if successful, link it in to the destination keyring if one is + * supplied. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring, + struct key *authkey) +{ + unsigned long prealloc; + int ret; + + if (keyring) { + ret = __key_link_begin(keyring, key->type, key->description, + &prealloc); + if (ret < 0) + return ret; + } + + ret = __key_instantiate_and_link(key, data, datalen, keyring, authkey, + &prealloc); + + if (keyring) + __key_link_end(keyring, key->type, prealloc); + + return ret; +} + +EXPORT_SYMBOL(key_instantiate_and_link); + +/** + * key_reject_and_link - Negatively instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @timeout: The timeout on the negative key. + * @error: The error to return when the key is hit. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Negatively instantiate a key that's in the uninstantiated state and, if + * successful, set its timeout and stored error and link it in to the + * destination keyring if one is supplied. The key and any links to the key + * will be automatically garbage collected after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return the stored error code (typically ENOKEY) until the negative + * key expires. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_reject_and_link(struct key *key, + unsigned timeout, + unsigned error, + struct key *keyring, + struct key *authkey) +{ + unsigned long prealloc; + struct timespec now; + int ret, awaken, link_ret = 0; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + link_ret = __key_link_begin(keyring, key->type, + key->description, &prealloc); + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* mark the key as being negatively instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_NEGATIVE, &key->flags); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + key->type_data.reject_error = -error; + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + key_schedule_gc(key->expiry + key_gc_delay); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + ret = 0; + + /* and link it into the destination keyring */ + if (keyring && link_ret == 0) + __key_link(keyring, key, &prealloc); + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + } + + mutex_unlock(&key_construction_mutex); + + if (keyring) + __key_link_end(keyring, key->type, prealloc); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret == 0 ? link_ret : ret; +} +EXPORT_SYMBOL(key_reject_and_link); + +/** + * key_put - Discard a reference to a key. + * @key: The key to discard a reference from. + * + * Discard a reference to a key, and when all the references are gone, we + * schedule the cleanup task to come and pull it out of the tree in process + * context at some later time. + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (atomic_dec_and_test(&key->usage)) + queue_work(system_nrt_wq, &key_gc_work); + } +} +EXPORT_SYMBOL(key_put); + +/* + * Find a key by its serial number. + */ +struct key *key_lookup(key_serial_t id) +{ + struct rb_node *n; + struct key *key; + + spin_lock(&key_serial_lock); + + /* search the tree for the specified key */ + n = key_serial_tree.rb_node; + while (n) { + key = rb_entry(n, struct key, serial_node); + + if (id < key->serial) + n = n->rb_left; + else if (id > key->serial) + n = n->rb_right; + else + goto found; + } + +not_found: + key = ERR_PTR(-ENOKEY); + goto error; + +found: + /* pretend it doesn't exist if it is awaiting deletion */ + if (atomic_read(&key->usage) == 0) + goto not_found; + + /* this races with key_put(), but that doesn't matter since key_put() + * doesn't actually change the key + */ + atomic_inc(&key->usage); + +error: + spin_unlock(&key_serial_lock); + return key; +} + +/* + * Find and lock the specified key type against removal. + * + * We return with the sem read-locked if successful. If the type wasn't + * available -ENOKEY is returned instead. + */ +struct key_type *key_type_lookup(const char *type) +{ + struct key_type *ktype; + + down_read(&key_types_sem); + + /* look up the key type to see if it's one of the registered kernel + * types */ + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_kernel_type; + } + + up_read(&key_types_sem); + ktype = ERR_PTR(-ENOKEY); + +found_kernel_type: + return ktype; +} + +void key_set_timeout(struct key *key, unsigned timeout) +{ + struct timespec now; + time_t expiry = 0; + + /* make the changes with the locks held to prevent races */ + down_write(&key->sem); + + if (timeout > 0) { + now = current_kernel_time(); + expiry = now.tv_sec + timeout; + } + + key->expiry = expiry; + key_schedule_gc(key->expiry + key_gc_delay); + + up_write(&key->sem); +} +EXPORT_SYMBOL_GPL(key_set_timeout); + +/* + * Unlock a key type locked by key_type_lookup(). + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); +} + +/* + * Attempt to update an existing key. + * + * The key is given to us with an incremented refcount that we need to discard + * if we get an error. + */ +static inline key_ref_t __key_update(key_ref_t key_ref, + const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + /* need write permission on the key to update it */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + if (ret == 0) + /* updating a negative key instantiates it */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + + if (ret < 0) + goto error; +out: + return key_ref; + +error: + key_put(key); + key_ref = ERR_PTR(ret); + goto out; +} + +/** + * key_create_or_update - Update or create and instantiate a key. + * @keyring_ref: A pointer to the destination keyring with possession flag. + * @type: The type of key. + * @description: The searchable description for the key. + * @payload: The data to use to instantiate or update the key. + * @plen: The length of @payload. + * @perm: The permissions mask for a new key. + * @flags: The quota flags for a new key. + * + * Search the destination keyring for a key of the same description and if one + * is found, update it, otherwise create and instantiate a new one and create a + * link to it from that keyring. + * + * If perm is KEY_PERM_UNDEF then an appropriate key permissions mask will be + * concocted. + * + * Returns a pointer to the new key if successful, -ENODEV if the key type + * wasn't available, -ENOTDIR if the keyring wasn't a keyring, -EACCES if the + * caller isn't permitted to modify the keyring or the LSM did not permit + * creation of the key. + * + * On success, the possession flag from the keyring ref will be tacked on to + * the key ref before it is returned. + */ +key_ref_t key_create_or_update(key_ref_t keyring_ref, + const char *type, + const char *description, + const void *payload, + size_t plen, + key_perm_t perm, + unsigned long flags) +{ + unsigned long prealloc; + const struct cred *cred = current_cred(); + struct key_type *ktype; + struct key *keyring, *key = NULL; + key_ref_t key_ref; + int ret; + + /* look up the key type to see if it's one of the registered kernel + * types */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + key_ref = ERR_PTR(-ENODEV); + goto error; + } + + key_ref = ERR_PTR(-EINVAL); + if (!ktype->match || !ktype->instantiate) + goto error_2; + + keyring = key_ref_to_ptr(keyring_ref); + + key_check(keyring); + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error_2; + + ret = __key_link_begin(keyring, ktype, description, &prealloc); + if (ret < 0) + goto error_2; + + /* if we're going to allocate a new key, we're going to have + * to modify the keyring */ + ret = key_permission(keyring_ref, KEY_WRITE); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_3; + } + + /* if it's possible to update this type of key, search for an existing + * key of the same type and description in the destination keyring and + * update that instead if possible + */ + if (ktype->update) { + key_ref = __keyring_search_one(keyring_ref, ktype, description, + 0); + if (!IS_ERR(key_ref)) + goto found_matching_key; + } + + /* if the client doesn't provide, decide on the permissions we want */ + if (perm == KEY_PERM_UNDEF) { + perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR; + + if (ktype->read) + perm |= KEY_POS_READ | KEY_USR_READ; + + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + } + + /* allocate a new key */ + key = key_alloc(ktype, description, cred->fsuid, cred->fsgid, cred, + perm, flags); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring, NULL, + &prealloc); + if (ret < 0) { + key_put(key); + key_ref = ERR_PTR(ret); + goto error_3; + } + + key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); + + error_3: + __key_link_end(keyring, ktype, prealloc); + error_2: + key_type_put(ktype); + error: + return key_ref; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + __key_link_end(keyring, ktype, prealloc); + key_type_put(ktype); + + key_ref = __key_update(key_ref, payload, plen); + goto error; +} +EXPORT_SYMBOL(key_create_or_update); + +/** + * key_update - Update a key's contents. + * @key_ref: The pointer (plus possession flag) to the key. + * @payload: The data to be used to update the key. + * @plen: The length of @payload. + * + * Attempt to update the contents of a key with the given payload data. The + * caller must be granted Write permission on the key. Negative keys can be + * instantiated by this method. + * + * Returns 0 on success, -EACCES if not permitted and -EOPNOTSUPP if the key + * type does not support updating. The key type may return other errors. + */ +int key_update(key_ref_t key_ref, const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + key_check(key); + + /* the key must be writable */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + goto error; + + /* attempt to update it if supported */ + ret = -EOPNOTSUPP; + if (key->type->update) { + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + if (ret == 0) + /* updating a negative key instantiates it */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + } + + error: + return ret; +} +EXPORT_SYMBOL(key_update); + +/** + * key_revoke - Revoke a key. + * @key: The key to be revoked. + * + * Mark a key as being revoked and ask the type to free up its resources. The + * revocation timeout is set and the key and all its links will be + * automatically garbage collected after key_gc_delay amount of time if they + * are not manually dealt with first. + */ +void key_revoke(struct key *key) +{ + struct timespec now; + time_t time; + + key_check(key); + + /* make sure no one's trying to change or use the key when we mark it + * - we tell lockdep that we might nest because we might be revoking an + * authorisation key whilst holding the sem on a key we've just + * instantiated + */ + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) && + key->type->revoke) + key->type->revoke(key); + + /* set the death time to no more than the expiry time */ + now = current_kernel_time(); + time = now.tv_sec; + if (key->revoked_at == 0 || key->revoked_at > time) { + key->revoked_at = time; + key_schedule_gc(key->revoked_at + key_gc_delay); + } + + up_write(&key->sem); +} +EXPORT_SYMBOL(key_revoke); + +/** + * register_key_type - Register a type of key. + * @ktype: The new key type. + * + * Register a new key type. + * + * Returns 0 on success or -EEXIST if a type of this name already exists. + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + memset(&ktype->lock_class, 0, sizeof(ktype->lock_class)); + + ret = -EEXIST; + down_write(&key_types_sem); + + /* disallow key types with the same name */ + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + /* store the type */ + list_add(&ktype->link, &key_types_list); + ret = 0; + +out: + up_write(&key_types_sem); + return ret; +} +EXPORT_SYMBOL(register_key_type); + +/** + * unregister_key_type - Unregister a type of key. + * @ktype: The key type. + * + * Unregister a key type and mark all the extant keys of this type as dead. + * Those keys of this type are then destroyed to get rid of their payloads and + * they and their links will be garbage collected as soon as possible. + */ +void unregister_key_type(struct key_type *ktype) +{ + down_write(&key_types_sem); + list_del_init(&ktype->link); + downgrade_write(&key_types_sem); + key_gc_keytype(ktype); + up_read(&key_types_sem); +} +EXPORT_SYMBOL(unregister_key_type); + +/* + * Initialise the key management state. + */ +void __init key_init(void) +{ + /* allocate a slab in which we can store keys */ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); + + /* add the special key types */ + list_add_tail(&key_type_keyring.link, &key_types_list); + list_add_tail(&key_type_dead.link, &key_types_list); + list_add_tail(&key_type_user.link, &key_types_list); + list_add_tail(&key_type_logon.link, &key_types_list); + + /* record the root user tracking */ + rb_link_node(&root_key_user.node, + NULL, + &key_user_tree.rb_node); + + rb_insert_color(&root_key_user.node, + &key_user_tree); +} diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 00000000..fb767c6c --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,1628 @@ +/* Userspace key control operations + * + * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/key.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/capability.h> +#include <linux/string.h> +#include <linux/err.h> +#include <linux/vmalloc.h> +#include <linux/security.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int key_get_type_from_user(char *type, + const char __user *_type, + unsigned len) +{ + int ret; + + ret = strncpy_from_user(type, _type, len); + if (ret < 0) + return ret; + if (ret == 0 || ret >= len) + return -EINVAL; + if (type[0] == '.') + return -EPERM; + type[len - 1] = '\0'; + return 0; +} + +/* + * Extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring. + * + * The keyring must be writable so that we can attach the key to it. + * + * If successful, the new key's serial number is returned, otherwise an error + * code is returned. + */ +SYSCALL_DEFINE5(add_key, const char __user *, _type, + const char __user *, _description, + const void __user *, _payload, + size_t, plen, + key_serial_t, ringid) +{ + key_ref_t keyring_ref, key_ref; + char type[32], *description; + void *payload; + long ret; + bool vm; + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* draw all the data into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the payload in if one was supplied */ + payload = NULL; + + vm = false; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error2; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error2; + } + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key_ref = key_create_or_update(keyring_ref, type, description, + payload, plen, KEY_PERM_UNDEF, + KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key_ref)) { + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); + } + else { + ret = PTR_ERR(key_ref); + } + + key_ref_put(keyring_ref); + error3: + if (!vm) + kfree(payload); + else + vfree(payload); + error2: + kfree(description); + error: + return ret; +} + +/* + * Search the process keyrings and keyring trees linked from those for a + * matching key. Keyrings must have appropriate Search permission to be + * searched. + * + * If a key is found, it will be attached to the destination keyring if there's + * one specified and the serial number of the key will be returned. + * + * If no key is found, /sbin/request-key will be invoked if _callout_info is + * non-NULL in an attempt to create a key. The _callout_info string will be + * passed to /sbin/request-key to aid with completing the request. If the + * _callout_info string is "" then it will be changed to "-". + */ +SYSCALL_DEFINE4(request_key, const char __user *, _type, + const char __user *, _description, + const char __user *, _callout_info, + key_serial_t, destringid) +{ + struct key_type *ktype; + struct key *key; + key_ref_t dest_ref; + size_t callout_len; + char type[32], *description, *callout_info; + long ret; + + /* pull the type into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + /* pull the description into kernel space */ + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the callout info into kernel space */ + callout_info = NULL; + callout_len = 0; + if (_callout_info) { + callout_info = strndup_user(_callout_info, PAGE_SIZE); + if (IS_ERR(callout_info)) { + ret = PTR_ERR(callout_info); + goto error2; + } + callout_len = strlen(callout_info); + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE, + KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key_and_link(ktype, description, callout_info, + callout_len, NULL, key_ref_to_ptr(dest_ref), + KEY_ALLOC_IN_QUOTA); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* wait for the key to finish being constructed */ + ret = wait_for_key_construction(key, 1); + if (ret < 0) + goto error6; + + ret = key->serial; + +error6: + key_put(key); +error5: + key_type_put(ktype); +error4: + key_ref_put(dest_ref); +error3: + kfree(callout_info); +error2: + kfree(description); +error: + return ret; +} + +/* + * Get the ID of the specified process keyring. + * + * The requested keyring must have search permission to be found. + * + * If successful, the ID of the requested keyring will be returned. + */ +long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + key_ref_t key_ref; + unsigned long lflags; + long ret; + + lflags = create ? KEY_LOOKUP_CREATE : 0; + key_ref = lookup_user_key(id, lflags, KEY_SEARCH); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Join a (named) session keyring. + * + * Create and join an anonymous session keyring or join a named session + * keyring, creating it if necessary. A named session keyring must have Search + * permission for it to be joined. Session keyrings without this permit will + * be skipped over. + * + * If successful, the ID of the joined session keyring will be returned. + */ +long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + name = strndup_user(_name, PAGE_SIZE); + if (IS_ERR(name)) { + ret = PTR_ERR(name); + goto error; + } + } + + /* join the session */ + ret = join_session_keyring(name); + kfree(name); + +error: + return ret; +} + +/* + * Update a key's data payload from the given data. + * + * The key must grant the caller Write permission and the key type must support + * updating for this to work. A negative key can be positively instantiated + * with this call. + * + * If successful, 0 will be returned. If the key type does not support + * updating, then -EOPNOTSUPP will be returned. + */ +long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + key_ref_t key_ref; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key_ref = lookup_user_key(id, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + /* update the key */ + ret = key_update(key_ref, payload, plen); + + key_ref_put(key_ref); +error2: + kfree(payload); +error: + return ret; +} + +/* + * Revoke a key. + * + * The key must be grant the caller Write or Setattr permission for this to + * work. The key type should give up its quota claim when revoked. The key + * and any links to the key will be automatically garbage collected after a + * certain amount of time (/proc/sys/kernel/keys/gc_delay). + * + * If successful, 0 is returned. + */ +long keyctl_revoke_key(key_serial_t id) +{ + key_ref_t key_ref; + long ret; + + key_ref = lookup_user_key(id, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + if (ret != -EACCES) + goto error; + key_ref = lookup_user_key(id, 0, KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + } + + key_revoke(key_ref_to_ptr(key_ref)); + ret = 0; + + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Clear the specified keyring, creating an empty process keyring if one of the + * special keyring IDs is used. + * + * The keyring must grant the caller Write permission for this to work. If + * successful, 0 will be returned. + */ +long keyctl_keyring_clear(key_serial_t ringid) +{ + key_ref_t keyring_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + + /* Root is permitted to invalidate certain special keyrings */ + if (capable(CAP_SYS_ADMIN)) { + keyring_ref = lookup_user_key(ringid, 0, 0); + if (IS_ERR(keyring_ref)) + goto error; + if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR, + &key_ref_to_ptr(keyring_ref)->flags)) + goto clear; + goto error_put; + } + + goto error; + } + +clear: + ret = keyring_clear(key_ref_to_ptr(keyring_ref)); +error_put: + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Create a link from a keyring to a key if there's no matching key in the + * keyring, otherwise replace the link to the matching key with a link to the + * new key. + * + * The key must grant the caller Link permission and the the keyring must grant + * the caller Write permission. Furthermore, if an additional link is created, + * the keyring's quota will be extended. + * + * If successful, 0 will be returned. + */ +long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_LINK); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_link(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); +error2: + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Unlink a key from a keyring. + * + * The keyring must grant the caller Write permission for this to work; the key + * itself need not grant the caller anything. If the last link to a key is + * removed then that key will be scheduled for destruction. + * + * If successful, 0 will be returned. + */ +long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(id, KEY_LOOKUP_FOR_UNLINK, 0); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_unlink(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); +error2: + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Return a description of a key to userspace. + * + * The key must grant the caller View permission for this to work. + * + * If there's a buffer, we place up to buflen bytes of data into it formatted + * in the following way: + * + * type;uid;gid;perm;description<NUL> + * + * If successful, we return the amount of description available, irrespective + * of how much we may have copied into the buffer. + */ +long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *tmpbuf; + long ret; + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW); + if (IS_ERR(key_ref)) { + /* viewing a key under construction is permitted if we have the + * authorisation token handy */ + if (PTR_ERR(key_ref) == -EACCES) { + instkey = key_get_instantiation_authkey(keyid); + if (!IS_ERR(instkey)) { + key_put(instkey); + key_ref = lookup_user_key(keyid, + KEY_LOOKUP_PARTIAL, + 0); + if (!IS_ERR(key_ref)) + goto okay; + } + } + + ret = PTR_ERR(key_ref); + goto error; + } + +okay: + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + key = key_ref_to_ptr(key_ref); + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%08x;%s", + key->type->name, + key->uid, + key->gid, + key->perm, + key->description ?: ""); + + /* include a NUL char at the end of the data */ + if (ret > PAGE_SIZE - 1) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); +error2: + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Search the specified keyring and any keyrings it links to for a matching + * key. Only keyrings that grant the caller Search permission will be searched + * (this includes the starting keyring). Only keys with Search permission can + * be found. + * + * If successful, the found key will be linked to the destination keyring if + * supplied and the key has Link permission, and the found key ID will be + * returned. + */ +long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + key_ref_t keyring_ref, key_ref, dest_ref; + char type[32], *description; + long ret; + + /* pull the type and description into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* get the keyring at which to begin the search */ + keyring_ref = lookup_user_key(ringid, 0, KEY_SEARCH); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error2; + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE, + KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key_ref = keyring_search(keyring_ref, ktype, description); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest_ref) { + ret = key_permission(key_ref, KEY_LINK); + if (ret < 0) + goto error6; + + ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(key_ref)); + if (ret < 0) + goto error6; + } + + ret = key_ref_to_ptr(key_ref)->serial; + +error6: + key_ref_put(key_ref); +error5: + key_type_put(ktype); +error4: + key_ref_put(dest_ref); +error3: + key_ref_put(keyring_ref); +error2: + kfree(description); +error: + return ret; +} + +/* + * Read a key's payload. + * + * The key must either grant the caller Read permission, or it must grant the + * caller Search permission when searched for from the process keyrings. + * + * If successful, we place up to buflen bytes of data into the buffer, if one + * is provided, and return the amount of data that is available in the key, + * irrespective of how much we copied into the buffer. + */ +long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) +{ + struct key *key; + key_ref_t key_ref; + long ret; + + /* find the key first */ + key_ref = lookup_user_key(keyid, 0, 0); + if (IS_ERR(key_ref)) { + ret = -ENOKEY; + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* see if we can read it directly */ + ret = key_permission(key_ref, KEY_READ); + if (ret == 0) + goto can_read_key; + if (ret != -EACCES) + goto error; + + /* we can't; see if it's searchable from this process's keyrings + * - we automatically take account of the fact that it may be + * dangling off an instantiation key + */ + if (!is_key_possessed(key_ref)) { + ret = -EACCES; + goto error2; + } + + /* the key is probably readable - now try to read it */ +can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + +error2: + key_put(key); +error: + return ret; +} + +/* + * Change the ownership of a key + * + * The key must grant the caller Setattr permission for this to work, though + * the key need not be fully instantiated yet. For the UID to be changed, or + * for the GID to be changed to a group the caller is not a member of, the + * caller must have sysadmin capability. If either uid or gid is -1 then that + * attribute is not changed. + * + * If the UID is to be changed, the new user must have sufficient quota to + * accept the key. The quota deduction will be removed from the old user to + * the new user should the attribute be changed. + * + * If successful, 0 will be returned. + */ +long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key_user *newowner, *zapowner = NULL; + struct key *key; + key_ref_t key_ref; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, + KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto error_put; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto error_put; + } + + /* change the UID */ + if (uid != (uid_t) -1 && uid != key->uid) { + ret = -ENOMEM; + newowner = key_user_lookup(uid, current_user_ns()); + if (!newowner) + goto error_put; + + /* transfer the quota burden to the new user */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&newowner->lock); + if (newowner->qnkeys + 1 >= maxkeys || + newowner->qnbytes + key->quotalen >= maxbytes || + newowner->qnbytes + key->quotalen < + newowner->qnbytes) + goto quota_overrun; + + newowner->qnkeys++; + newowner->qnbytes += key->quotalen; + spin_unlock(&newowner->lock); + + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + atomic_inc(&newowner->nkeys); + + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + atomic_dec(&key->user->nikeys); + atomic_inc(&newowner->nikeys); + } + + zapowner = key->user; + key->user = newowner; + key->uid = uid; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + +error_put: + up_write(&key->sem); + key_put(key); + if (zapowner) + key_user_put(zapowner); +error: + return ret; + +quota_overrun: + spin_unlock(&newowner->lock); + zapowner = newowner; + ret = -EDQUOT; + goto error_put; +} + +/* + * Change the permission mask on a key. + * + * The key must grant the caller Setattr permission for this to work, though + * the key need not be fully instantiated yet. If the caller does not have + * sysadmin capability, it may only change the permission on keys that it owns. + */ +long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + key_ref_t key_ref; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, + KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chmod races */ + ret = -EACCES; + down_write(&key->sem); + + /* if we're not the sysadmin, we can only change a key that we own */ + if (capable(CAP_SYS_ADMIN) || key->uid == current_fsuid()) { + key->perm = perm; + ret = 0; + } + + up_write(&key->sem); + key_put(key); +error: + return ret; +} + +/* + * Get the destination keyring for instantiation and check that the caller has + * Write permission on it. + */ +static long get_instantiation_keyring(key_serial_t ringid, + struct request_key_auth *rka, + struct key **_dest_keyring) +{ + key_ref_t dkref; + + *_dest_keyring = NULL; + + /* just return a NULL pointer if we weren't asked to make a link */ + if (ringid == 0) + return 0; + + /* if a specific keyring is nominated by ID, then use that */ + if (ringid > 0) { + dkref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(dkref)) + return PTR_ERR(dkref); + *_dest_keyring = key_ref_to_ptr(dkref); + return 0; + } + + if (ringid == KEY_SPEC_REQKEY_AUTH_KEY) + return -EINVAL; + + /* otherwise specify the destination keyring recorded in the + * authorisation key (any KEY_SPEC_*_KEYRING) */ + if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) { + *_dest_keyring = key_get(rka->dest_keyring); + return 0; + } + + return -ENOKEY; +} + +/* + * Change the request_key authorisation key on the current process. + */ +static int keyctl_change_reqkey_auth(struct key *key) +{ + struct cred *new; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + key_put(new->request_key_auth); + new->request_key_auth = key_get(key); + + return commit_creds(new); +} + +/* + * Copy the iovec data from userspace + */ +static long copy_from_user_iovec(void *buffer, const struct iovec *iov, + unsigned ioc) +{ + for (; ioc > 0; ioc--) { + if (copy_from_user(buffer, iov->iov_base, iov->iov_len) != 0) + return -EFAULT; + buffer += iov->iov_len; + iov++; + } + return 0; +} + +/* + * Instantiate a key with the specified payload and link the key into the + * destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key_common(key_serial_t id, + const struct iovec *payload_iov, + unsigned ioc, + size_t plen, + key_serial_t ringid) +{ + const struct cred *cred = current_cred(); + struct request_key_auth *rka; + struct key *instkey, *dest_keyring; + void *payload; + long ret; + bool vm = false; + + kenter("%d,,%zu,%d", id, plen, ringid); + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = cred->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (payload_iov) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error; + } + + ret = copy_from_user_iovec(payload, payload_iov, ioc); + if (ret < 0) + goto error2; + } + + /* find the destination keyring amongst those belonging to the + * requesting task */ + ret = get_instantiation_keyring(ringid, rka, &dest_keyring); + if (ret < 0) + goto error2; + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(rka->target_key, payload, plen, + dest_keyring, instkey); + + key_put(dest_keyring); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) + keyctl_change_reqkey_auth(NULL); + +error2: + if (!vm) + kfree(payload); + else + vfree(payload); +error: + return ret; +} + +/* + * Instantiate a key with the specified payload and link the key into the + * destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + if (_payload && plen) { + struct iovec iov[1] = { + [0].iov_base = (void __user *)_payload, + [0].iov_len = plen + }; + + return keyctl_instantiate_key_common(id, iov, 1, plen, ringid); + } + + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * Instantiate a key with the specified multipart payload and link the key into + * the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key_iov(key_serial_t id, + const struct iovec __user *_payload_iov, + unsigned ioc, + key_serial_t ringid) +{ + struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; + long ret; + + if (_payload_iov == 0 || ioc == 0) + goto no_payload; + + ret = rw_copy_check_uvector(WRITE, _payload_iov, ioc, + ARRAY_SIZE(iovstack), iovstack, &iov, 1); + if (ret < 0) + return ret; + if (ret == 0) + goto no_payload_free; + + ret = keyctl_instantiate_key_common(id, iov, ioc, ret, ringid); + + if (iov != iovstack) + kfree(iov); + return ret; + +no_payload_free: + if (iov != iovstack) + kfree(iov); +no_payload: + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * Negatively instantiate the key with the given timeout (in seconds) and link + * the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * The key and any links to the key will be automatically garbage collected + * after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return -ENOKEY until the negative key expires. + * + * If successful, 0 will be returned. + */ +long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid) +{ + return keyctl_reject_key(id, timeout, ENOKEY, ringid); +} + +/* + * Negatively instantiate the key with the given timeout (in seconds) and error + * code and link the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * The key and any links to the key will be automatically garbage collected + * after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return the specified error code until the negative key expires. + * + * If successful, 0 will be returned. + */ +long keyctl_reject_key(key_serial_t id, unsigned timeout, unsigned error, + key_serial_t ringid) +{ + const struct cred *cred = current_cred(); + struct request_key_auth *rka; + struct key *instkey, *dest_keyring; + long ret; + + kenter("%d,%u,%u,%d", id, timeout, error, ringid); + + /* must be a valid error code and mustn't be a kernel special */ + if (error <= 0 || + error >= MAX_ERRNO || + error == ERESTARTSYS || + error == ERESTARTNOINTR || + error == ERESTARTNOHAND || + error == ERESTART_RESTARTBLOCK) + return -EINVAL; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = cred->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* find the destination keyring if present (which must also be + * writable) */ + ret = get_instantiation_keyring(ringid, rka, &dest_keyring); + if (ret < 0) + goto error; + + /* instantiate the key and link it into a keyring */ + ret = key_reject_and_link(rka->target_key, timeout, error, + dest_keyring, instkey); + + key_put(dest_keyring); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) + keyctl_change_reqkey_auth(NULL); + +error: + return ret; +} + +/* + * Read or set the default keyring in which request_key() will cache keys and + * return the old setting. + * + * If a process keyring is specified then this will be created if it doesn't + * yet exist. The old setting will be returned if successful. + */ +long keyctl_set_reqkey_keyring(int reqkey_defl) +{ + struct cred *new; + int ret, old_setting; + + old_setting = current_cred_xxx(jit_keyring); + + if (reqkey_defl == KEY_REQKEY_DEFL_NO_CHANGE) + return old_setting; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + switch (reqkey_defl) { + case KEY_REQKEY_DEFL_THREAD_KEYRING: + ret = install_thread_keyring_to_cred(new); + if (ret < 0) + goto error; + goto set; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + ret = install_process_keyring_to_cred(new); + if (ret < 0) { + if (ret != -EEXIST) + goto error; + ret = 0; + } + goto set; + + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_SESSION_KEYRING: + case KEY_REQKEY_DEFL_USER_KEYRING: + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + case KEY_REQKEY_DEFL_REQUESTOR_KEYRING: + goto set; + + case KEY_REQKEY_DEFL_NO_CHANGE: + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + ret = -EINVAL; + goto error; + } + +set: + new->jit_keyring = reqkey_defl; + commit_creds(new); + return old_setting; +error: + abort_creds(new); + return ret; +} + +/* + * Set or clear the timeout on a key. + * + * Either the key must grant the caller Setattr permission or else the caller + * must hold an instantiation authorisation token for the key. + * + * The timeout is either 0 to clear the timeout, or a number of seconds from + * the current time. The key and any links to the key will be automatically + * garbage collected after the timeout expires. + * + * If successful, 0 is returned. + */ +long keyctl_set_timeout(key_serial_t id, unsigned timeout) +{ + struct key *key, *instkey; + key_ref_t key_ref; + long ret; + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, + KEY_SETATTR); + if (IS_ERR(key_ref)) { + /* setting the timeout on a key under construction is permitted + * if we have the authorisation token handy */ + if (PTR_ERR(key_ref) == -EACCES) { + instkey = key_get_instantiation_authkey(id); + if (!IS_ERR(instkey)) { + key_put(instkey); + key_ref = lookup_user_key(id, + KEY_LOOKUP_PARTIAL, + 0); + if (!IS_ERR(key_ref)) + goto okay; + } + } + + ret = PTR_ERR(key_ref); + goto error; + } + +okay: + key = key_ref_to_ptr(key_ref); + key_set_timeout(key, timeout); + key_put(key); + + ret = 0; +error: + return ret; +} + +/* + * Assume (or clear) the authority to instantiate the specified key. + * + * This sets the authoritative token currently in force for key instantiation. + * This must be done for a key to be instantiated. It has the effect of making + * available all the keys from the caller of the request_key() that created a + * key to request_key() calls made by the caller of this function. + * + * The caller must have the instantiation key in their process keyrings with a + * Search permission grant available to the caller. + * + * If the ID given is 0, then the setting will be cleared and 0 returned. + * + * If the ID given has a matching an authorisation key, then that key will be + * set and its ID will be returned. The authorisation key can be read to get + * the callout information passed to request_key(). + */ +long keyctl_assume_authority(key_serial_t id) +{ + struct key *authkey; + long ret; + + /* special key IDs aren't permitted */ + ret = -EINVAL; + if (id < 0) + goto error; + + /* we divest ourselves of authority if given an ID of 0 */ + if (id == 0) { + ret = keyctl_change_reqkey_auth(NULL); + goto error; + } + + /* attempt to assume the authority temporarily granted to us whilst we + * instantiate the specified key + * - the authorisation key must be in the current task's keyrings + * somewhere + */ + authkey = key_get_instantiation_authkey(id); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error; + } + + ret = keyctl_change_reqkey_auth(authkey); + if (ret < 0) + goto error; + key_put(authkey); + + ret = authkey->serial; +error: + return ret; +} + +/* + * Get a key's the LSM security label. + * + * The key must grant the caller View permission for this to work. + * + * If there's a buffer, then up to buflen bytes of data will be placed into it. + * + * If successful, the amount of information available will be returned, + * irrespective of how much was copied (including the terminal NUL). + */ +long keyctl_get_security(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *context; + long ret; + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW); + if (IS_ERR(key_ref)) { + if (PTR_ERR(key_ref) != -EACCES) + return PTR_ERR(key_ref); + + /* viewing a key under construction is also permitted if we + * have the authorisation token handy */ + instkey = key_get_instantiation_authkey(keyid); + if (IS_ERR(instkey)) + return PTR_ERR(instkey); + key_put(instkey); + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0); + if (IS_ERR(key_ref)) + return PTR_ERR(key_ref); + } + + key = key_ref_to_ptr(key_ref); + ret = security_key_getsecurity(key, &context); + if (ret == 0) { + /* if no information was returned, give userspace an empty + * string */ + ret = 1; + if (buffer && buflen > 0 && + copy_to_user(buffer, "", 1) != 0) + ret = -EFAULT; + } else if (ret > 0) { + /* return as much data as there's room for */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, context, buflen) != 0) + ret = -EFAULT; + } + + kfree(context); + } + + key_ref_put(key_ref); + return ret; +} + +/* + * Attempt to install the calling process's session keyring on the process's + * parent process. + * + * The keyring must exist and must grant the caller LINK permission, and the + * parent process must be single-threaded and must have the same effective + * ownership as this process and mustn't be SUID/SGID. + * + * The keyring will be emplaced on the parent when it next resumes userspace. + * + * If successful, 0 will be returned. + */ +long keyctl_session_to_parent(void) +{ +#ifdef TIF_NOTIFY_RESUME + struct task_struct *me, *parent; + const struct cred *mycred, *pcred; + struct cred *cred, *oldcred; + key_ref_t keyring_r; + int ret; + + keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK); + if (IS_ERR(keyring_r)) + return PTR_ERR(keyring_r); + + /* our parent is going to need a new cred struct, a new tgcred struct + * and new security data, so we allocate them here to prevent ENOMEM in + * our parent */ + ret = -ENOMEM; + cred = cred_alloc_blank(); + if (!cred) + goto error_keyring; + + cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r); + keyring_r = NULL; + + me = current; + rcu_read_lock(); + write_lock_irq(&tasklist_lock); + + parent = me->real_parent; + ret = -EPERM; + + /* the parent mustn't be init and mustn't be a kernel thread */ + if (parent->pid <= 1 || !parent->mm) + goto not_permitted; + + /* the parent must be single threaded */ + if (!thread_group_empty(parent)) + goto not_permitted; + + /* the parent and the child must have different session keyrings or + * there's no point */ + mycred = current_cred(); + pcred = __task_cred(parent); + if (mycred == pcred || + mycred->tgcred->session_keyring == pcred->tgcred->session_keyring) + goto already_same; + + /* the parent must have the same effective ownership and mustn't be + * SUID/SGID */ + if (pcred->uid != mycred->euid || + pcred->euid != mycred->euid || + pcred->suid != mycred->euid || + pcred->gid != mycred->egid || + pcred->egid != mycred->egid || + pcred->sgid != mycred->egid) + goto not_permitted; + + /* the keyrings must have the same UID */ + if ((pcred->tgcred->session_keyring && + pcred->tgcred->session_keyring->uid != mycred->euid) || + mycred->tgcred->session_keyring->uid != mycred->euid) + goto not_permitted; + + /* if there's an already pending keyring replacement, then we replace + * that */ + oldcred = parent->replacement_session_keyring; + + /* the replacement session keyring is applied just prior to userspace + * restarting */ + parent->replacement_session_keyring = cred; + cred = NULL; + set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME); + + write_unlock_irq(&tasklist_lock); + rcu_read_unlock(); + if (oldcred) + put_cred(oldcred); + return 0; + +already_same: + ret = 0; +not_permitted: + write_unlock_irq(&tasklist_lock); + rcu_read_unlock(); + put_cred(cred); + return ret; + +error_keyring: + key_ref_put(keyring_r); + return ret; + +#else /* !TIF_NOTIFY_RESUME */ + /* + * To be removed when TIF_NOTIFY_RESUME has been implemented on + * m68k/xtensa + */ +#warning TIF_NOTIFY_RESUME not implemented + return -EOPNOTSUPP; +#endif /* !TIF_NOTIFY_RESUME */ +} + +/* + * The key control system call + */ +SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, + unsigned long, arg4, unsigned long, arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg2); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout((key_serial_t) arg2, + (unsigned) arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority((key_serial_t) arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_SESSION_TO_PARENT: + return keyctl_session_to_parent(); + + case KEYCTL_REJECT: + return keyctl_reject_key((key_serial_t) arg2, + (unsigned) arg3, + (unsigned) arg4, + (key_serial_t) arg5); + + case KEYCTL_INSTANTIATE_IOV: + return keyctl_instantiate_key_iov( + (key_serial_t) arg2, + (const struct iovec __user *) arg3, + (unsigned) arg4, + (key_serial_t) arg5); + + default: + return -EOPNOTSUPP; + } +} diff --git a/security/keys/keyring.c b/security/keys/keyring.c new file mode 100644 index 00000000..d605f752 --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,1232 @@ +/* Keyring handling + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <keys/keyring-type.h> +#include <linux/uaccess.h> +#include "internal.h" + +#define rcu_dereference_locked_keyring(keyring) \ + (rcu_dereference_protected( \ + (keyring)->payload.subscriptions, \ + rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) + +#define KEY_LINK_FIXQUOTA 1UL + +/* + * When plumbing the depths of the key tree, this sets a hard limit + * set on how deep we're willing to go. + */ +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/* + * We keep all named keyrings in a hash to speed looking them up. + */ +#define KEYRING_NAME_HASH_SIZE (1 << 5) + +static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; +static DEFINE_RWLOCK(keyring_name_lock); + +static inline unsigned keyring_hash(const char *desc) +{ + unsigned bucket = 0; + + for (; *desc; desc++) + bucket += (unsigned char)*desc; + + return bucket & (KEYRING_NAME_HASH_SIZE - 1); +} + +/* + * The keyring key type definition. Keyrings are simply keys of this type and + * can be treated as ordinary keys in addition to having their own special + * operations. + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen); +static int keyring_match(const struct key *keyring, const void *criterion); +static void keyring_revoke(struct key *keyring); +static void keyring_destroy(struct key *keyring); +static void keyring_describe(const struct key *keyring, struct seq_file *m); +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen); + +struct key_type key_type_keyring = { + .name = "keyring", + .def_datalen = sizeof(struct keyring_list), + .instantiate = keyring_instantiate, + .match = keyring_match, + .revoke = keyring_revoke, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; +EXPORT_SYMBOL(key_type_keyring); + +/* + * Semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle. + */ +static DECLARE_RWSEM(keyring_serialise_link_sem); + +/* + * Publish the name of a keyring so that it can be found by name (if it has + * one). + */ +static void keyring_publish_name(struct key *keyring) +{ + int bucket; + + if (keyring->description) { + bucket = keyring_hash(keyring->description); + + write_lock(&keyring_name_lock); + + if (!keyring_name_hash[bucket].next) + INIT_LIST_HEAD(&keyring_name_hash[bucket]); + + list_add_tail(&keyring->type_data.link, + &keyring_name_hash[bucket]); + + write_unlock(&keyring_name_lock); + } +} + +/* + * Initialise a keyring. + * + * Returns 0 on success, -EINVAL if given any data. + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen == 0) { + /* make the keyring available by name if it has one */ + keyring_publish_name(keyring); + ret = 0; + } + + return ret; +} + +/* + * Match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + return keyring->description && + strcmp(keyring->description, description) == 0; +} + +/* + * Clean up a keyring when it is destroyed. Unpublish its name if it had one + * and dispose of its data. + */ +static void keyring_destroy(struct key *keyring) +{ + struct keyring_list *klist; + int loop; + + if (keyring->description) { + write_lock(&keyring_name_lock); + + if (keyring->type_data.link.next != NULL && + !list_empty(&keyring->type_data.link)) + list_del(&keyring->type_data.link); + + write_unlock(&keyring_name_lock); + } + + klist = rcu_dereference_check(keyring->payload.subscriptions, + atomic_read(&keyring->usage) == 0); + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } +} + +/* + * Describe a keyring for /proc. + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_list *klist; + + if (keyring->description) + seq_puts(m, keyring->description); + else + seq_puts(m, "[anon]"); + + if (key_is_instantiated(keyring)) { + rcu_read_lock(); + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + rcu_read_unlock(); + } +} + +/* + * Read a list of key IDs from the keyring's contents in binary form + * + * The keyring's semaphore is read-locked by the caller. + */ +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen) +{ + struct keyring_list *klist; + struct key *key; + size_t qty, tmp; + int loop, ret; + + ret = 0; + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* calculate how much data we could return */ + qty = klist->nkeys * sizeof(key_serial_t); + + if (buffer && buflen > 0) { + if (buflen > qty) + buflen = qty; + + /* copy the IDs of the subscribed keys into the + * buffer */ + ret = -EFAULT; + + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + tmp = sizeof(key_serial_t); + if (tmp > buflen) + tmp = buflen; + + if (copy_to_user(buffer, + &key->serial, + tmp) != 0) + goto error; + + buflen -= tmp; + if (buflen == 0) + break; + buffer += tmp; + } + } + + ret = qty; + } + +error: + return ret; +} + +/* + * Allocate a keyring and link into the destination keyring. + */ +struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + const struct cred *cred, unsigned long flags, + struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, cred, + (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, + flags); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL); + if (ret < 0) { + key_put(keyring); + keyring = ERR_PTR(ret); + } + } + + return keyring; +} + +/** + * keyring_search_aux - Search a keyring tree for a key matching some criteria + * @keyring_ref: A pointer to the keyring with possession indicator. + * @cred: The credentials to use for permissions checks. + * @type: The type of key to search for. + * @description: Parameter for @match. + * @match: Function to rule on whether or not a key is the one required. + * @no_state_check: Don't check if a matching key is bad + * + * Search the supplied keyring tree for a key that matches the criteria given. + * The root keyring and any linked keyrings must grant Search permission to the + * caller to be searchable and keys can only be found if they too grant Search + * to the caller. The possession flag on the root keyring pointer controls use + * of the possessor bits in permissions checking of the entire tree. In + * addition, the LSM gets to forbid keyring searches and key matches. + * + * The search is performed as a breadth-then-depth search up to the prescribed + * limit (KEYRING_SEARCH_MAX_DEPTH). + * + * Keys are matched to the type provided and are then filtered by the match + * function, which is given the description to use in any way it sees fit. The + * match function may use any attributes of a key that it wishes to to + * determine the match. Normally the match function from the key type would be + * used. + * + * RCU is used to prevent the keyring key lists from disappearing without the + * need to take lots of locks. + * + * Returns a pointer to the found key and increments the key usage count if + * successful; -EAGAIN if no matching keys were found, or if expired or revoked + * keys were found; -ENOKEY if only negative keys were found; -ENOTDIR if the + * specified keyring wasn't a keyring. + * + * In the case of a successful return, the possession attribute from + * @keyring_ref is propagated to the returned key reference. + */ +key_ref_t keyring_search_aux(key_ref_t keyring_ref, + const struct cred *cred, + struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check) +{ + struct { + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + unsigned long possessed, kflags; + struct key *keyring, *key; + key_ref_t key_ref; + long err; + int sp, nkeys, kix; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + err = key_task_permission(keyring_ref, cred, KEY_SEARCH); + if (err < 0) { + key_ref = ERR_PTR(err); + goto error; + } + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + rcu_read_lock(); + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* firstly we should check to see if this top-level keyring is what we + * are looking for */ + key_ref = ERR_PTR(-EAGAIN); + kflags = keyring->flags; + if (keyring->type == type && match(keyring, description)) { + key = keyring; + if (no_state_check) + goto found; + + /* check it isn't negative and hasn't expired or been + * revoked */ + if (kflags & (1 << KEY_FLAG_REVOKED)) + goto error_2; + if (key->expiry && now.tv_sec >= key->expiry) + goto error_2; + key_ref = ERR_PTR(key->type_data.reject_error); + if (kflags & (1 << KEY_FLAG_NEGATIVE)) + goto error_2; + goto found; + } + + /* otherwise, the top keyring must not be revoked, expired, or + * negatively instantiated if we are to search it */ + key_ref = ERR_PTR(-EAGAIN); + if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || + (keyring->expiry && now.tv_sec >= keyring->expiry)) + goto error_2; + + /* start processing a new keyring */ +descend: + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(keyring->payload.subscriptions); + if (!keylist) + goto not_this_keyring; + + /* iterate through the keys in this keyring first */ + nkeys = keylist->nkeys; + smp_rmb(); + for (kix = 0; kix < nkeys; kix++) { + key = keylist->keys[kix]; + kflags = key->flags; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (!no_state_check) { + if (kflags & (1 << KEY_FLAG_REVOKED)) + continue; + + if (key->expiry && now.tv_sec >= key->expiry) + continue; + } + + /* keys that don't match */ + if (!match(key, description)) + continue; + + /* key must have search permissions */ + if (key_task_permission(make_key_ref(key, possessed), + cred, KEY_SEARCH) < 0) + continue; + + if (no_state_check) + goto found; + + /* we set a different error code if we pass a negative key */ + if (kflags & (1 << KEY_FLAG_NEGATIVE)) { + err = key->type_data.reject_error; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; +ascend: + nkeys = keylist->nkeys; + smp_rmb(); + for (; kix < nkeys; kix++) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + continue; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + continue; + + if (key_task_permission(make_key_ref(key, possessed), + cred, KEY_SEARCH) < 0) + continue; + + /* stack the current position */ + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ +not_this_keyring: + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + key_ref = ERR_PTR(err); + goto error_2; + + /* we found a viable match */ +found: + atomic_inc(&key->usage); + key_check(key); + key_ref = make_key_ref(key, possessed); +error_2: + rcu_read_unlock(); +error: + return key_ref; +} + +/** + * keyring_search - Search the supplied keyring tree for a matching key + * @keyring: The root of the keyring tree to be searched. + * @type: The type of keyring we want to find. + * @description: The name of the keyring we want to find. + * + * As keyring_search_aux() above, but using the current task's credentials and + * type's default matching function. + */ +key_ref_t keyring_search(key_ref_t keyring, + struct key_type *type, + const char *description) +{ + if (!type->match) + return ERR_PTR(-ENOKEY); + + return keyring_search_aux(keyring, current->cred, + type, description, type->match, false); +} +EXPORT_SYMBOL(keyring_search); + +/* + * Search the given keyring only (no recursion). + * + * The caller must guarantee that the keyring is a keyring and that the + * permission is granted to search the keyring as no check is made here. + * + * RCU is used to make it unnecessary to lock the keyring key list here. + * + * Returns a pointer to the found key with usage count incremented if + * successful and returns -ENOKEY if not found. Revoked keys and keys not + * providing the requested permission are skipped over. + * + * If successful, the possession indicator is propagated from the keyring ref + * to the returned key reference. + */ +key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + unsigned long possessed; + struct key *keyring, *key; + int nkeys, loop; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + + rcu_read_lock(); + + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) { + nkeys = klist->nkeys; + smp_rmb(); + for (loop = 0; loop < nkeys ; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + (!key->type->match || + key->type->match(key, description)) && + key_permission(make_key_ref(key, possessed), + perm) == 0 && + !test_bit(KEY_FLAG_REVOKED, &key->flags) + ) + goto found; + } + } + + rcu_read_unlock(); + return ERR_PTR(-ENOKEY); + +found: + atomic_inc(&key->usage); + rcu_read_unlock(); + return make_key_ref(key, possessed); +} + +/* + * Find a keyring with the specified name. + * + * All named keyrings in the current user namespace are searched, provided they + * grant Search permission directly to the caller (unless this check is + * skipped). Keyrings whose usage points have reached zero or who have been + * revoked are skipped. + * + * Returns a pointer to the keyring with the keyring's refcount having being + * incremented on success. -ENOKEY is returned if a key could not be found. + */ +struct key *find_keyring_by_name(const char *name, bool skip_perm_check) +{ + struct key *keyring; + int bucket; + + if (!name) + return ERR_PTR(-EINVAL); + + bucket = keyring_hash(name); + + read_lock(&keyring_name_lock); + + if (keyring_name_hash[bucket].next) { + /* search this hash bucket for a keyring with a matching name + * that's readable and that hasn't been revoked */ + list_for_each_entry(keyring, + &keyring_name_hash[bucket], + type_data.link + ) { + if (keyring->user->user_ns != current_user_ns()) + continue; + + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!skip_perm_check && + key_permission(make_key_ref(keyring, 0), + KEY_SEARCH) < 0) + continue; + + /* we've got a match but we might end up racing with + * key_cleanup() if the keyring is currently 'dead' + * (ie. it has a zero usage count) */ + if (!atomic_inc_not_zero(&keyring->usage)) + continue; + goto out; + } + } + + keyring = ERR_PTR(-ENOKEY); +out: + read_unlock(&keyring_name_lock); + return keyring; +} + +/* + * See if a cycle will will be created by inserting acyclic tree B in acyclic + * tree A at the topmost level (ie: as a direct child of A). + * + * Since we are adding B to A at the top level, checking for cycles should just + * be a matter of seeing if node A is somewhere in tree B. + */ +static int keyring_detect_cycle(struct key *A, struct key *B) +{ + struct { + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, nkeys, kix, ret; + + rcu_read_lock(); + + ret = -EDEADLK; + if (A == B) + goto cycle_detected; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ +descend: + if (test_bit(KEY_FLAG_REVOKED, &subtree->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(subtree->payload.subscriptions); + if (!keylist) + goto not_this_keyring; + kix = 0; + +ascend: + /* iterate through the remaining keys in this keyring */ + nkeys = keylist->nkeys; + smp_rmb(); + for (; kix < nkeys; kix++) { + key = keylist->keys[kix]; + + if (key == A) + goto cycle_detected; + + /* recursively check nested keyrings */ + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto too_deep; + + /* stack the current position */ + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + subtree = key; + goto descend; + } + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ +not_this_keyring: + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + +error: + rcu_read_unlock(); + return ret; + +too_deep: + ret = -ELOOP; + goto error; + +cycle_detected: + ret = -EDEADLK; + goto error; +} + +/* + * Dispose of a keyring list after the RCU grace period, freeing the unlinked + * key + */ +static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist = + container_of(rcu, struct keyring_list, rcu); + + if (klist->delkey != USHRT_MAX) + key_put(klist->keys[klist->delkey]); + kfree(klist); +} + +/* + * Preallocate memory so that a key can be linked into to a keyring. + */ +int __key_link_begin(struct key *keyring, const struct key_type *type, + const char *description, unsigned long *_prealloc) + __acquires(&keyring->sem) +{ + struct keyring_list *klist, *nklist; + unsigned long prealloc; + unsigned max; + size_t size; + int loop, ret; + + kenter("%d,%s,%s,", key_serial(keyring), type->name, description); + + if (keyring->type != &key_type_keyring) + return -ENOTDIR; + + down_write(&keyring->sem); + + ret = -EKEYREVOKED; + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto error_krsem; + + /* serialise link/link calls to prevent parallel calls causing a cycle + * when linking two keyring in opposite orders */ + if (type == &key_type_keyring) + down_write(&keyring_serialise_link_sem); + + klist = rcu_dereference_locked_keyring(keyring); + + /* see if there's a matching key we can displace */ + if (klist && klist->nkeys > 0) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + if (klist->keys[loop]->type == type && + strcmp(klist->keys[loop]->description, + description) == 0 + ) { + /* found a match - we'll replace this one with + * the new key */ + size = sizeof(struct key *) * klist->maxkeys; + size += sizeof(*klist); + BUG_ON(size > PAGE_SIZE); + + ret = -ENOMEM; + nklist = kmemdup(klist, size, GFP_KERNEL); + if (!nklist) + goto error_sem; + + /* note replacement slot */ + klist->delkey = nklist->delkey = loop; + prealloc = (unsigned long)nklist; + goto done; + } + } + } + + /* check that we aren't going to overrun the user's quota */ + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error_sem; + + if (klist && klist->nkeys < klist->maxkeys) { + /* there's sufficient slack space to append directly */ + nklist = NULL; + prealloc = KEY_LINK_FIXQUOTA; + } else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + if (max > USHRT_MAX - 1) + goto error_quota; + size = sizeof(*klist) + sizeof(struct key *) * max; + if (size > PAGE_SIZE) + goto error_quota; + + ret = -ENOMEM; + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + goto error_quota; + + nklist->maxkeys = max; + if (klist) { + memcpy(nklist->keys, klist->keys, + sizeof(struct key *) * klist->nkeys); + nklist->delkey = klist->nkeys; + nklist->nkeys = klist->nkeys + 1; + klist->delkey = USHRT_MAX; + } else { + nklist->nkeys = 1; + nklist->delkey = 0; + } + + /* add the key into the new space */ + nklist->keys[nklist->delkey] = NULL; + } + + prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; +done: + *_prealloc = prealloc; + kleave(" = 0"); + return 0; + +error_quota: + /* undo the quota changes */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); +error_sem: + if (type == &key_type_keyring) + up_write(&keyring_serialise_link_sem); +error_krsem: + up_write(&keyring->sem); + kleave(" = %d", ret); + return ret; +} + +/* + * Check already instantiated keys aren't going to be a problem. + * + * The caller must have called __key_link_begin(). Don't need to call this for + * keys that were created since __key_link_begin() was called. + */ +int __key_link_check_live_key(struct key *keyring, struct key *key) +{ + if (key->type == &key_type_keyring) + /* check that we aren't going to create a cycle by linking one + * keyring to another */ + return keyring_detect_cycle(keyring, key); + return 0; +} + +/* + * Link a key into to a keyring. + * + * Must be called with __key_link_begin() having being called. Discards any + * already extant link to matching key if there is one, so that each keyring + * holds at most one link to any given key of a particular type+description + * combination. + */ +void __key_link(struct key *keyring, struct key *key, + unsigned long *_prealloc) +{ + struct keyring_list *klist, *nklist; + + nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA); + *_prealloc = 0; + + kenter("%d,%d,%p", keyring->serial, key->serial, nklist); + + klist = rcu_dereference_locked_keyring(keyring); + + atomic_inc(&key->usage); + + /* there's a matching key we can displace or an empty slot in a newly + * allocated list we can fill */ + if (nklist) { + kdebug("replace %hu/%hu/%hu", + nklist->delkey, nklist->nkeys, nklist->maxkeys); + + nklist->keys[nklist->delkey] = key; + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + /* dispose of the old keyring list and, if there was one, the + * displaced key */ + if (klist) { + kdebug("dispose %hu/%hu/%hu", + klist->delkey, klist->nkeys, klist->maxkeys); + call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + } + } else { + /* there's sufficient slack space to append directly */ + klist->keys[klist->nkeys] = key; + smp_wmb(); + klist->nkeys++; + } +} + +/* + * Finish linking a key into to a keyring. + * + * Must be called with __key_link_begin() having being called. + */ +void __key_link_end(struct key *keyring, struct key_type *type, + unsigned long prealloc) + __releases(&keyring->sem) +{ + BUG_ON(type == NULL); + BUG_ON(type->name == NULL); + kenter("%d,%s,%lx", keyring->serial, type->name, prealloc); + + if (type == &key_type_keyring) + up_write(&keyring_serialise_link_sem); + + if (prealloc) { + if (prealloc & KEY_LINK_FIXQUOTA) + key_payload_reserve(keyring, + keyring->datalen - + KEYQUOTA_LINK_BYTES); + kfree((struct keyring_list *)(prealloc & ~KEY_LINK_FIXQUOTA)); + } + up_write(&keyring->sem); +} + +/** + * key_link - Link a key to a keyring + * @keyring: The keyring to make the link in. + * @key: The key to link to. + * + * Make a link in a keyring to a key, such that the keyring holds a reference + * on that key and the key can potentially be found by searching that keyring. + * + * This function will write-lock the keyring's semaphore and will consume some + * of the user's key data quota to hold the link. + * + * Returns 0 if successful, -ENOTDIR if the keyring isn't a keyring, + * -EKEYREVOKED if the keyring has been revoked, -ENFILE if the keyring is + * full, -EDQUOT if there is insufficient key data quota remaining to add + * another link or -ENOMEM if there's insufficient memory. + * + * It is assumed that the caller has checked that it is permitted for a link to + * be made (the keyring should have Write permission and the key Link + * permission). + */ +int key_link(struct key *keyring, struct key *key) +{ + unsigned long prealloc; + int ret; + + key_check(keyring); + key_check(key); + + ret = __key_link_begin(keyring, key->type, key->description, &prealloc); + if (ret == 0) { + ret = __key_link_check_live_key(keyring, key); + if (ret == 0) + __key_link(keyring, key, &prealloc); + __key_link_end(keyring, key->type, prealloc); + } + + return ret; +} +EXPORT_SYMBOL(key_link); + +/** + * key_unlink - Unlink the first link to a key from a keyring. + * @keyring: The keyring to remove the link from. + * @key: The key the link is to. + * + * Remove a link from a keyring to a key. + * + * This function will write-lock the keyring's semaphore. + * + * Returns 0 if successful, -ENOTDIR if the keyring isn't a keyring, -ENOENT if + * the key isn't linked to by the keyring or -ENOMEM if there's insufficient + * memory. + * + * It is assumed that the caller has checked that it is permitted for a link to + * be removed (the keyring should have Write permission; no permissions are + * required on the key). + */ +int key_unlink(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + int loop, ret; + + key_check(keyring); + key_check(key); + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* search the keyring for the key */ + for (loop = 0; loop < klist->nkeys; loop++) + if (klist->keys[loop] == key) + goto key_is_present; + } + + up_write(&keyring->sem); + ret = -ENOENT; + goto error; + +key_is_present: + /* we need to copy the key list for RCU purposes */ + nklist = kmalloc(sizeof(*klist) + + sizeof(struct key *) * klist->maxkeys, + GFP_KERNEL); + if (!nklist) + goto nomem; + nklist->maxkeys = klist->maxkeys; + nklist->nkeys = klist->nkeys - 1; + + if (loop > 0) + memcpy(&nklist->keys[0], + &klist->keys[0], + loop * sizeof(struct key *)); + + if (loop < nklist->nkeys) + memcpy(&nklist->keys[loop], + &klist->keys[loop + 1], + (nklist->nkeys - loop) * sizeof(struct key *)); + + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + up_write(&keyring->sem); + + /* schedule for later cleanup */ + klist->delkey = loop; + call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + + ret = 0; + +error: + return ret; +nomem: + ret = -ENOMEM; + up_write(&keyring->sem); + goto error; +} +EXPORT_SYMBOL(key_unlink); + +/* + * Dispose of a keyring list after the RCU grace period, releasing the keys it + * links to. + */ +static void keyring_clear_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist; + int loop; + + klist = container_of(rcu, struct keyring_list, rcu); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); +} + +/** + * keyring_clear - Clear a keyring + * @keyring: The keyring to clear. + * + * Clear the contents of the specified keyring. + * + * Returns 0 if successful or -ENOTDIR if the keyring isn't a keyring. + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int ret; + + ret = -ENOTDIR; + if (keyring->type == &key_type_keyring) { + /* detach the pointer block with the locks held */ + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list)); + + rcu_assign_pointer(keyring->payload.subscriptions, + NULL); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + + ret = 0; + } + + return ret; +} +EXPORT_SYMBOL(keyring_clear); + +/* + * Dispose of the links from a revoked keyring. + * + * This is called with the key sem write-locked. + */ +static void keyring_revoke(struct key *keyring) +{ + struct keyring_list *klist; + + klist = rcu_dereference_locked_keyring(keyring); + + /* adjust the quota */ + key_payload_reserve(keyring, 0); + + if (klist) { + rcu_assign_pointer(keyring->payload.subscriptions, NULL); + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + } +} + +/* + * Determine whether a key is dead. + */ +static bool key_is_dead(struct key *key, time_t limit) +{ + return test_bit(KEY_FLAG_DEAD, &key->flags) || + (key->expiry > 0 && key->expiry <= limit); +} + +/* + * Collect garbage from the contents of a keyring, replacing the old list with + * a new one with the pointers all shuffled down. + * + * Dead keys are classed as oned that are flagged as being dead or are revoked, + * expired or negative keys that were revoked or expired before the specified + * limit. + */ +void keyring_gc(struct key *keyring, time_t limit) +{ + struct keyring_list *klist, *new; + struct key *key; + int loop, keep, max; + + kenter("{%x,%s}", key_serial(keyring), keyring->description); + + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (!klist) + goto no_klist; + + /* work out how many subscriptions we're keeping */ + keep = 0; + for (loop = klist->nkeys - 1; loop >= 0; loop--) + if (!key_is_dead(klist->keys[loop], limit)) + keep++; + + if (keep == klist->nkeys) + goto just_return; + + /* allocate a new keyring payload */ + max = roundup(keep, 4); + new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *), + GFP_KERNEL); + if (!new) + goto nomem; + new->maxkeys = max; + new->nkeys = 0; + new->delkey = 0; + + /* install the live keys + * - must take care as expired keys may be updated back to life + */ + keep = 0; + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + key = klist->keys[loop]; + if (!key_is_dead(key, limit)) { + if (keep >= max) + goto discard_new; + new->keys[keep++] = key_get(key); + } + } + new->nkeys = keep; + + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list) + + KEYQUOTA_LINK_BYTES * keep); + + if (keep == 0) { + rcu_assign_pointer(keyring->payload.subscriptions, NULL); + kfree(new); + } else { + rcu_assign_pointer(keyring->payload.subscriptions, new); + } + + up_write(&keyring->sem); + + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + kleave(" [yes]"); + return; + +discard_new: + new->nkeys = keep; + keyring_clear_rcu_disposal(&new->rcu); + up_write(&keyring->sem); + kleave(" [discard]"); + return; + +just_return: + up_write(&keyring->sem); + kleave(" [no dead]"); + return; + +no_klist: + up_write(&keyring->sem); + kleave(" [no_klist]"); + return; + +nomem: + up_write(&keyring->sem); + kleave(" [oom]"); +} diff --git a/security/keys/permission.c b/security/keys/permission.c new file mode 100644 index 00000000..c35b5229 --- /dev/null +++ b/security/keys/permission.c @@ -0,0 +1,118 @@ +/* Key permission checking + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/security.h> +#include "internal.h" + +/** + * key_task_permission - Check a key can be used + * @key_ref: The key to check. + * @cred: The credentials to use. + * @perm: The permissions to check for. + * + * Check to see whether permission is granted to use a key in the desired way, + * but permit the security modules to override. + * + * The caller must hold either a ref on cred or must hold the RCU readlock. + * + * Returns 0 if successful, -EACCES if access is denied based on the + * permissions bits or the LSM check. + */ +int key_task_permission(const key_ref_t key_ref, const struct cred *cred, + key_perm_t perm) +{ + struct key *key; + key_perm_t kperm; + int ret; + + key = key_ref_to_ptr(key_ref); + + if (key->user->user_ns != cred->user->user_ns) + goto use_other_perms; + + /* use the second 8-bits of permissions for keys the caller owns */ + if (key->uid == cred->fsuid) { + kperm = key->perm >> 16; + goto use_these_perms; + } + + /* use the third 8-bits of permissions for keys the caller has a group + * membership in common with */ + if (key->gid != -1 && key->perm & KEY_GRP_ALL) { + if (key->gid == cred->fsgid) { + kperm = key->perm >> 8; + goto use_these_perms; + } + + ret = groups_search(cred->group_info, key->gid); + if (ret) { + kperm = key->perm >> 8; + goto use_these_perms; + } + } + +use_other_perms: + + /* otherwise use the least-significant 8-bits */ + kperm = key->perm; + +use_these_perms: + + /* use the top 8-bits of permissions for keys the caller possesses + * - possessor permissions are additive with other permissions + */ + if (is_key_possessed(key_ref)) + kperm |= key->perm >> 24; + + kperm = kperm & perm & KEY_ALL; + + if (kperm != perm) + return -EACCES; + + /* let LSM be the final arbiter */ + return security_key_permission(key_ref, cred, perm); +} +EXPORT_SYMBOL(key_task_permission); + +/** + * key_validate - Validate a key. + * @key: The key to be validated. + * + * Check that a key is valid, returning 0 if the key is okay, -EKEYREVOKED if + * the key's type has been removed or if the key has been revoked or + * -EKEYEXPIRED if the key has expired. + */ +int key_validate(struct key *key) +{ + struct timespec now; + int ret = 0; + + if (key) { + /* check it's still accessible */ + ret = -EKEYREVOKED; + if (test_bit(KEY_FLAG_REVOKED, &key->flags) || + test_bit(KEY_FLAG_DEAD, &key->flags)) + goto error; + + /* check it hasn't expired */ + ret = 0; + if (key->expiry) { + now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + ret = -EKEYEXPIRED; + } + } + +error: + return ret; +} +EXPORT_SYMBOL(key_validate); diff --git a/security/keys/proc.c b/security/keys/proc.c new file mode 100644 index 00000000..49bbc979 --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,352 @@ +/* procfs files for key database enumeration + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/errno.h> +#include "internal.h" + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static const struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static const struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int proc_key_users_open(struct inode *inode, struct file *file); +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos); +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_key_users_stop(struct seq_file *p, void *v); +static int proc_key_users_show(struct seq_file *m, void *v); + +static const struct seq_operations proc_key_users_ops = { + .start = proc_key_users_start, + .next = proc_key_users_next, + .stop = proc_key_users_stop, + .show = proc_key_users_show, +}; + +static const struct file_operations proc_key_users_fops = { + .open = proc_key_users_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* + * Declare the /proc files. + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + p = proc_create("keys", 0, NULL, &proc_keys_fops); + if (!p) + panic("Cannot create /proc/keys\n"); +#endif + + p = proc_create("key-users", 0, NULL, &proc_key_users_fops); + if (!p) + panic("Cannot create /proc/key-users\n"); + + return 0; +} + +__initcall(key_proc_init); + +/* + * Implement "/proc/keys" to provide a list of the keys on the system that + * grant View permission to the caller. + */ +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + +static struct rb_node *key_serial_next(struct rb_node *n) +{ + struct user_namespace *user_ns = current_user_ns(); + + n = rb_next(n); + while (n) { + struct key *key = rb_entry(n, struct key, serial_node); + if (key->user->user_ns == user_ns) + break; + n = rb_next(n); + } + return n; +} + +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); +} + +static struct key *find_ge_key(key_serial_t id) +{ + struct user_namespace *user_ns = current_user_ns(); + struct rb_node *n = key_serial_tree.rb_node; + struct key *minkey = NULL; + + while (n) { + struct key *key = rb_entry(n, struct key, serial_node); + if (id < key->serial) { + if (!minkey || minkey->serial > key->serial) + minkey = key; + n = n->rb_left; + } else if (id > key->serial) { + n = n->rb_right; + } else { + minkey = key; + break; + } + key = NULL; + } + + if (!minkey) + return NULL; + + for (;;) { + if (minkey->user->user_ns == user_ns) + return minkey; + n = rb_next(&minkey->serial_node); + if (!n) + return NULL; + minkey = rb_entry(n, struct key, serial_node); + } +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) + __acquires(key_serial_lock) +{ + key_serial_t pos = *_pos; + struct key *key; + + spin_lock(&key_serial_lock); + + if (*_pos > INT_MAX) + return NULL; + key = find_ge_key(pos); + if (!key) + return NULL; + *_pos = key->serial; + return &key->serial_node; +} + +static inline key_serial_t key_node_serial(struct rb_node *n) +{ + struct key *key = rb_entry(n, struct key, serial_node); + return key->serial; +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + struct rb_node *n; + + n = key_serial_next(v); + if (n) + *_pos = key_node_serial(n); + return n; +} + +static void proc_keys_stop(struct seq_file *p, void *v) + __releases(key_serial_lock) +{ + spin_unlock(&key_serial_lock); +} + +static int proc_keys_show(struct seq_file *m, void *v) +{ + const struct cred *cred = current_cred(); + struct rb_node *_p = v; + struct key *key = rb_entry(_p, struct key, serial_node); + struct timespec now; + unsigned long timo; + key_ref_t key_ref, skey_ref; + char xbuf[12]; + int rc; + + key_ref = make_key_ref(key, 0); + + /* determine if the key is possessed by this process (a test we can + * skip if the key does not indicate the possessor can view it + */ + if (key->perm & KEY_POS_VIEW) { + skey_ref = search_my_process_keyrings(key->type, key, + lookup_user_key_possessed, + true, cred); + if (!IS_ERR(skey_ref)) { + key_ref_put(skey_ref); + key_ref = make_key_ref(key, 1); + } + } + + /* check whether the current task is allowed to view the key (assuming + * non-possession) + * - the caller holds a spinlock, and thus the RCU read lock, making our + * access to __current_cred() safe + */ + rc = key_task_permission(key_ref, cred, KEY_VIEW); + if (rc < 0) + return 0; + + now = current_kernel_time(); + + rcu_read_lock(); + + /* come up with a suitable timeout value */ + if (key->expiry == 0) { + memcpy(xbuf, "perm", 5); + } else if (now.tv_sec >= key->expiry) { + memcpy(xbuf, "expd", 5); + } else { + timo = key->expiry - now.tv_sec; + + if (timo < 60) + sprintf(xbuf, "%lus", timo); + else if (timo < 60*60) + sprintf(xbuf, "%lum", timo / 60); + else if (timo < 60*60*24) + sprintf(xbuf, "%luh", timo / (60*60)); + else if (timo < 60*60*24*7) + sprintf(xbuf, "%lud", timo / (60*60*24)); + else + sprintf(xbuf, "%luw", timo / (60*60*24*7)); + } + +#define showflag(KEY, LETTER, FLAG) \ + (test_bit(FLAG, &(KEY)->flags) ? LETTER : '-') + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ", + key->serial, + showflag(key, 'I', KEY_FLAG_INSTANTIATED), + showflag(key, 'R', KEY_FLAG_REVOKED), + showflag(key, 'D', KEY_FLAG_DEAD), + showflag(key, 'Q', KEY_FLAG_IN_QUOTA), + showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT), + showflag(key, 'N', KEY_FLAG_NEGATIVE), + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + +#undef showflag + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + rcu_read_unlock(); + return 0; +} + +#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ + +static struct rb_node *__key_user_next(struct rb_node *n) +{ + while (n) { + struct key_user *user = rb_entry(n, struct key_user, node); + if (user->user_ns == current_user_ns()) + break; + n = rb_next(n); + } + return n; +} + +static struct rb_node *key_user_next(struct rb_node *n) +{ + return __key_user_next(rb_next(n)); +} + +static struct rb_node *key_user_first(struct rb_root *r) +{ + struct rb_node *n = rb_first(r); + return __key_user_next(n); +} + +/* + * Implement "/proc/key-users" to provides a list of the key users and their + * quotas. + */ +static int proc_key_users_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_key_users_ops); +} + +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) + __acquires(key_user_lock) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_user_lock); + + _p = key_user_first(&key_user_tree); + while (pos > 0 && _p) { + pos--; + _p = key_user_next(_p); + } + + return _p; +} + +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return key_user_next((struct rb_node *)v); +} + +static void proc_key_users_stop(struct seq_file *p, void *v) + __releases(key_user_lock) +{ + spin_unlock(&key_user_lock); +} + +static int proc_key_users_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key_user *user = rb_entry(_p, struct key_user, node); + unsigned maxkeys = (user->uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", + user->uid, + atomic_read(&user->usage), + atomic_read(&user->nkeys), + atomic_read(&user->nikeys), + user->qnkeys, + maxkeys, + user->qnbytes, + maxbytes); + + return 0; +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 00000000..be7ecb20 --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,878 @@ +/* Manage a process's keyrings + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/security.h> +#include <linux/user_namespace.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* Session keyring create vs join semaphore */ +static DEFINE_MUTEX(key_session_mutex); + +/* User keyring creation semaphore */ +static DEFINE_MUTEX(key_user_keyring_mutex); + +/* The root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .cons_lock = __MUTEX_INITIALIZER(root_key_user.cons_lock), + .lock = __SPIN_LOCK_UNLOCKED(root_key_user.lock), + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, + .user_ns = &init_user_ns, +}; + +/* + * Install the user and user session keyrings for the current process's UID. + */ +int install_user_keyrings(void) +{ + struct user_struct *user; + const struct cred *cred; + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + cred = current_cred(); + user = cred->user; + + kenter("%p{%u}", user, user->uid); + + if (user->uid_keyring) { + kleave(" = 0 [exist]"); + return 0; + } + + mutex_lock(&key_user_keyring_mutex); + ret = 0; + + if (!user->uid_keyring) { + /* get the UID-specific keyring + * - there may be one in existence already as it may have been + * pinned by a session, but the user_struct pointing to it + * may have been destroyed by setuid */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(uid_keyring)) { + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, + cred, KEY_ALLOC_IN_QUOTA, + NULL); + if (IS_ERR(uid_keyring)) { + ret = PTR_ERR(uid_keyring); + goto error; + } + } + + /* get a default session keyring (which might also exist + * already) */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(session_keyring)) { + session_keyring = + keyring_alloc(buf, user->uid, (gid_t) -1, + cred, KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error_release; + } + + /* we install a link from the user session keyring to + * the user keyring */ + ret = key_link(session_keyring, uid_keyring); + if (ret < 0) + goto error_release_both; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + } + + mutex_unlock(&key_user_keyring_mutex); + kleave(" = 0"); + return 0; + +error_release_both: + key_put(session_keyring); +error_release: + key_put(uid_keyring); +error: + mutex_unlock(&key_user_keyring_mutex); + kleave(" = %d", ret); + return ret; +} + +/* + * Install a fresh thread keyring directly to new credentials. This keyring is + * allowed to overrun the quota. + */ +int install_thread_keyring_to_cred(struct cred *new) +{ + struct key *keyring; + + keyring = keyring_alloc("_tid", new->uid, new->gid, new, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + + new->thread_keyring = keyring; + return 0; +} + +/* + * Install a fresh thread keyring, discarding the old one. + */ +static int install_thread_keyring(void) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + BUG_ON(new->thread_keyring); + + ret = install_thread_keyring_to_cred(new); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} + +/* + * Install a process keyring directly to a credentials struct. + * + * Returns -EEXIST if there was already a process keyring, 0 if one installed, + * and other value on any other error + */ +int install_process_keyring_to_cred(struct cred *new) +{ + struct key *keyring; + int ret; + + if (new->tgcred->process_keyring) + return -EEXIST; + + keyring = keyring_alloc("_pid", new->uid, new->gid, + new, KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + + spin_lock_irq(&new->tgcred->lock); + if (!new->tgcred->process_keyring) { + new->tgcred->process_keyring = keyring; + keyring = NULL; + ret = 0; + } else { + ret = -EEXIST; + } + spin_unlock_irq(&new->tgcred->lock); + key_put(keyring); + return ret; +} + +/* + * Make sure a process keyring is installed for the current process. The + * existing process keyring is not replaced. + * + * Returns 0 if there is a process keyring by the end of this function, some + * error otherwise. + */ +static int install_process_keyring(void) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_process_keyring_to_cred(new); + if (ret < 0) { + abort_creds(new); + return ret != -EEXIST ? ret : 0; + } + + return commit_creds(new); +} + +/* + * Install a session keyring directly to a credentials struct. + */ +int install_session_keyring_to_cred(struct cred *cred, struct key *keyring) +{ + unsigned long flags; + struct key *old; + + might_sleep(); + + /* create an empty session keyring */ + if (!keyring) { + flags = KEY_ALLOC_QUOTA_OVERRUN; + if (cred->tgcred->session_keyring) + flags = KEY_ALLOC_IN_QUOTA; + + keyring = keyring_alloc("_ses", cred->uid, cred->gid, + cred, flags, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + } else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + spin_lock_irq(&cred->tgcred->lock); + old = cred->tgcred->session_keyring; + rcu_assign_pointer(cred->tgcred->session_keyring, keyring); + spin_unlock_irq(&cred->tgcred->lock); + + /* we're using RCU on the pointer, but there's no point synchronising + * on it if it didn't previously point to anything */ + if (old) { + synchronize_rcu(); + key_put(old); + } + + return 0; +} + +/* + * Install a session keyring, discarding the old one. If a keyring is not + * supplied, an empty one is invented. + */ +static int install_session_keyring(struct key *keyring) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_session_keyring_to_cred(new, keyring); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} + +/* + * Handle the fsuid changing. + */ +void key_fsuid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + BUG_ON(!tsk->cred); + if (tsk->cred->thread_keyring) { + down_write(&tsk->cred->thread_keyring->sem); + tsk->cred->thread_keyring->uid = tsk->cred->fsuid; + up_write(&tsk->cred->thread_keyring->sem); + } +} + +/* + * Handle the fsgid changing. + */ +void key_fsgid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + BUG_ON(!tsk->cred); + if (tsk->cred->thread_keyring) { + down_write(&tsk->cred->thread_keyring->sem); + tsk->cred->thread_keyring->gid = tsk->cred->fsgid; + up_write(&tsk->cred->thread_keyring->sem); + } +} + +/* + * Search the process keyrings attached to the supplied cred for the first + * matching key. + * + * The search criteria are the type and the match function. The description is + * given to the match function as a parameter, but doesn't otherwise influence + * the search. Typically the match function will compare the description + * parameter to the key's description. + * + * This can only search keyrings that grant Search permission to the supplied + * credentials. Keyrings linked to searched keyrings will also be searched if + * they grant Search permission too. Keys can only be found if they grant + * Search permission to the credentials. + * + * Returns a pointer to the key with the key usage count incremented if + * successful, -EAGAIN if we didn't find any matching key or -ENOKEY if we only + * matched negative keys. + * + * In the case of a successful return, the possession attribute is set on the + * returned key reference. + */ +key_ref_t search_my_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check, + const struct cred *cred) +{ + key_ref_t key_ref, ret, err; + + /* we want to return -EAGAIN or -ENOKEY if any of the keyrings were + * searchable, but we failed to find a key or we found a negative key; + * otherwise we want to return a sample error (probably -EACCES) if + * none of the keyrings were searchable + * + * in terms of priority: success > -ENOKEY > -EAGAIN > other error + */ + key_ref = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (cred->thread_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->thread_keyring, 1), + cred, type, description, match, no_state_check); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* search the process keyring second */ + if (cred->tgcred->process_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->tgcred->process_keyring, 1), + cred, type, description, match, no_state_check); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* search the session keyring */ + if (cred->tgcred->session_keyring) { + rcu_read_lock(); + key_ref = keyring_search_aux( + make_key_ref(rcu_dereference( + cred->tgcred->session_keyring), + 1), + cred, type, description, match, no_state_check); + rcu_read_unlock(); + + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + /* or search the user-session keyring */ + else if (cred->user->session_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->user->session_keyring, 1), + cred, type, description, match, no_state_check); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* no key - decide on the error we're going to go for */ + key_ref = ret ? ret : err; + +found: + return key_ref; +} + +/* + * 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 + * the keys attached to the assumed authorisation key using its credentials if + * one is available. + * + * Return same as search_my_process_keyrings(). + */ +key_ref_t search_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + const struct cred *cred) +{ + struct request_key_auth *rka; + key_ref_t key_ref, ret = ERR_PTR(-EACCES), err; + + might_sleep(); + + key_ref = search_my_process_keyrings(type, description, match, + false, cred); + if (!IS_ERR(key_ref)) + goto found; + err = key_ref; + + /* if this process has an instantiation authorisation key, then we also + * search the keyrings of the process mentioned there + * - we don't permit access to request_key auth keys via this method + */ + if (cred->request_key_auth && + cred == current_cred() && + type != &key_type_request_key_auth + ) { + /* defend against the auth key being revoked */ + down_read(&cred->request_key_auth->sem); + + if (key_validate(cred->request_key_auth) == 0) { + rka = cred->request_key_auth->payload.data; + + key_ref = search_process_keyrings(type, description, + match, rka->cred); + + up_read(&cred->request_key_auth->sem); + + if (!IS_ERR(key_ref)) + goto found; + + ret = key_ref; + } else { + up_read(&cred->request_key_auth->sem); + } + } + + /* no key - decide on the error we're going to go for */ + if (err == ERR_PTR(-ENOKEY) || ret == ERR_PTR(-ENOKEY)) + key_ref = ERR_PTR(-ENOKEY); + else if (err == ERR_PTR(-EACCES)) + key_ref = ret; + else + key_ref = err; + +found: + return key_ref; +} + +/* + * See if the key we're looking at is the target key. + */ +int lookup_user_key_possessed(const struct key *key, const void *target) +{ + return key == target; +} + +/* + * Look up a key ID given us by userspace with a given permissions mask to get + * the key it refers to. + * + * Flags can be passed to request that special keyrings be created if referred + * to directly, to permit partially constructed keys to be found and to skip + * validity and permission checks on the found key. + * + * Returns a pointer to the key with an incremented usage count if successful; + * -EINVAL if the key ID is invalid; -ENOKEY if the key ID does not correspond + * to a key or the best found key was a negative key; -EKEYREVOKED or + * -EKEYEXPIRED if the best found key was revoked or expired; -EACCES if the + * found key doesn't grant the requested permit or the LSM denied access to it; + * or -ENOMEM if a special keyring couldn't be created. + * + * In the case of a successful return, the possession attribute is set on the + * returned key reference. + */ +key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags, + key_perm_t perm) +{ + struct request_key_auth *rka; + const struct cred *cred; + struct key *key; + key_ref_t key_ref, skey_ref; + int ret; + +try_again: + cred = get_current_cred(); + key_ref = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!cred->thread_keyring) { + if (!(lflags & KEY_LOOKUP_CREATE)) + goto error; + + ret = install_thread_keyring(); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error; + } + goto reget_creds; + } + + key = cred->thread_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!cred->tgcred->process_keyring) { + if (!(lflags & KEY_LOOKUP_CREATE)) + goto error; + + ret = install_process_keyring(); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error; + } + goto reget_creds; + } + + key = cred->tgcred->process_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!cred->tgcred->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_user_keyrings(); + if (ret < 0) + goto error; + if (lflags & KEY_LOOKUP_CREATE) + ret = join_session_keyring(NULL); + else + ret = install_session_keyring( + cred->user->session_keyring); + + if (ret < 0) + goto error; + goto reget_creds; + } else if (cred->tgcred->session_keyring == + cred->user->session_keyring && + lflags & KEY_LOOKUP_CREATE) { + ret = join_session_keyring(NULL); + if (ret < 0) + goto error; + goto reget_creds; + } + + rcu_read_lock(); + key = rcu_dereference(cred->tgcred->session_keyring); + atomic_inc(&key->usage); + rcu_read_unlock(); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_KEYRING: + if (!cred->user->uid_keyring) { + ret = install_user_keyrings(); + if (ret < 0) + goto error; + } + + key = cred->user->uid_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + if (!cred->user->session_keyring) { + ret = install_user_keyrings(); + if (ret < 0) + goto error; + } + + key = cred->user->session_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key_ref = ERR_PTR(-EINVAL); + goto error; + + case KEY_SPEC_REQKEY_AUTH_KEY: + key = cred->request_key_auth; + if (!key) + goto error; + + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_REQUESTOR_KEYRING: + if (!cred->request_key_auth) + goto error; + + down_read(&cred->request_key_auth->sem); + if (test_bit(KEY_FLAG_REVOKED, + &cred->request_key_auth->flags)) { + key_ref = ERR_PTR(-EKEYREVOKED); + key = NULL; + } else { + rka = cred->request_key_auth->payload.data; + key = rka->dest_keyring; + atomic_inc(&key->usage); + } + up_read(&cred->request_key_auth->sem); + if (!key) + goto error; + key_ref = make_key_ref(key, 1); + break; + + default: + key_ref = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error; + } + + key_ref = make_key_ref(key, 0); + + /* check to see if we possess the key */ + skey_ref = search_process_keyrings(key->type, key, + lookup_user_key_possessed, + cred); + + if (!IS_ERR(skey_ref)) { + key_put(key); + key_ref = skey_ref; + } + + break; + } + + /* unlink does not use the nominated key in any way, so can skip all + * the permission checks as it is only concerned with the keyring */ + if (lflags & KEY_LOOKUP_FOR_UNLINK) { + ret = 0; + goto error; + } + + if (!(lflags & KEY_LOOKUP_PARTIAL)) { + ret = wait_for_key_construction(key, true); + switch (ret) { + case -ERESTARTSYS: + goto invalid_key; + default: + if (perm) + goto invalid_key; + case 0: + break; + } + } else if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!(lflags & KEY_LOOKUP_PARTIAL) && + !test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + goto invalid_key; + + /* check the permissions */ + ret = key_task_permission(key_ref, cred, perm); + if (ret < 0) + goto invalid_key; + +error: + put_cred(cred); + return key_ref; + +invalid_key: + key_ref_put(key_ref); + key_ref = ERR_PTR(ret); + goto error; + + /* if we attempted to install a keyring, then it may have caused new + * creds to be installed */ +reget_creds: + put_cred(cred); + goto try_again; +} + +/* + * Join the named keyring as the session keyring if possible else attempt to + * create a new one of that name and join that. + * + * If the name is NULL, an empty anonymous keyring will be installed as the + * session keyring. + * + * Named session keyrings are joined with a semaphore held to prevent the + * keyrings from going away whilst the attempt is made to going them and also + * to prevent a race in creating compatible session keyrings. + */ +long join_session_keyring(const char *name) +{ + const struct cred *old; + struct cred *new; + struct key *keyring; + long ret, serial; + + /* only permit this if there's a single thread in the thread group - + * this avoids us having to adjust the creds on all threads and risking + * ENOMEM */ + if (!current_is_single_threaded()) + return -EMLINK; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + old = current_cred(); + + /* if no name is provided, install an anonymous keyring */ + if (!name) { + ret = install_session_keyring_to_cred(new, NULL); + if (ret < 0) + goto error; + + serial = new->tgcred->session_keyring->serial; + ret = commit_creds(new); + if (ret == 0) + ret = serial; + goto okay; + } + + /* allow the user to join or create a named keyring */ + mutex_lock(&key_session_mutex); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, false); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, old->uid, old->gid, old, + KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } else if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* we've got a keyring - now to install it */ + ret = install_session_keyring_to_cred(new, keyring); + if (ret < 0) + goto error2; + + commit_creds(new); + mutex_unlock(&key_session_mutex); + + ret = keyring->serial; + key_put(keyring); +okay: + return ret; + +error2: + mutex_unlock(&key_session_mutex); +error: + abort_creds(new); + return ret; +} + +/* + * Replace a process's session keyring on behalf of one of its children when + * the target process is about to resume userspace execution. + */ +void key_replace_session_keyring(void) +{ + const struct cred *old; + struct cred *new; + + if (!current->replacement_session_keyring) + return; + + write_lock_irq(&tasklist_lock); + new = current->replacement_session_keyring; + current->replacement_session_keyring = NULL; + write_unlock_irq(&tasklist_lock); + + if (!new) + return; + + old = current_cred(); + new-> uid = old-> uid; + new-> euid = old-> euid; + new-> suid = old-> suid; + new->fsuid = old->fsuid; + new-> gid = old-> gid; + new-> egid = old-> egid; + new-> sgid = old-> sgid; + new->fsgid = old->fsgid; + new->user = get_uid(old->user); + new->user_ns = new->user->user_ns; + new->group_info = get_group_info(old->group_info); + + new->securebits = old->securebits; + new->cap_inheritable = old->cap_inheritable; + new->cap_permitted = old->cap_permitted; + new->cap_effective = old->cap_effective; + new->cap_bset = old->cap_bset; + + new->jit_keyring = old->jit_keyring; + new->thread_keyring = key_get(old->thread_keyring); + new->tgcred->tgid = old->tgcred->tgid; + new->tgcred->process_keyring = key_get(old->tgcred->process_keyring); + + security_transfer_creds(new, old); + + commit_creds(new); +} diff --git a/security/keys/request_key.c b/security/keys/request_key.c new file mode 100644 index 00000000..cc379031 --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,713 @@ +/* Request a key from userspace + * + * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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. + * + * See Documentation/security/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include <linux/keyctl.h> +#include <linux/slab.h> +#include "internal.h" + +#define key_negative_timeout 60 /* default timeout on a negative key's existence */ + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_wait_bit(void *flags) +{ + schedule(); + return 0; +} + +/* + * wait_on_bit() sleep function for interruptible waiting + */ +static int key_wait_bit_intr(void *flags) +{ + schedule(); + return signal_pending(current) ? -ERESTARTSYS : 0; +} + +/** + * complete_request_key - Complete the construction of a key. + * @cons: The key construction record. + * @error: The success or failute of the construction. + * + * Complete the attempt to construct a key. The key will be negated + * if an error is indicated. The authorisation key will be revoked + * unconditionally. + */ +void complete_request_key(struct key_construction *cons, int error) +{ + kenter("{%d,%d},%d", cons->key->serial, cons->authkey->serial, error); + + if (error < 0) + key_negate_and_link(cons->key, key_negative_timeout, NULL, + cons->authkey); + else + key_revoke(cons->authkey); + + key_put(cons->key); + key_put(cons->authkey); + kfree(cons); +} +EXPORT_SYMBOL(complete_request_key); + +/* + * Initialise a usermode helper that is going to have a specific session + * keyring. + * + * This is called in context of freshly forked kthread before kernel_execve(), + * so we can simply install the desired session_keyring at this point. + */ +static int umh_keys_init(struct subprocess_info *info, struct cred *cred) +{ + struct key *keyring = info->data; + + return install_session_keyring_to_cred(cred, keyring); +} + +/* + * Clean up a usermode helper with session keyring. + */ +static void umh_keys_cleanup(struct subprocess_info *info) +{ + struct key *keyring = info->data; + key_put(keyring); +} + +/* + * Call a usermode helper with a specific session keyring. + */ +static int call_usermodehelper_keys(char *path, char **argv, char **envp, + struct key *session_keyring, int wait) +{ + gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL; + struct subprocess_info *info = + call_usermodehelper_setup(path, argv, envp, gfp_mask); + + if (!info) + return -ENOMEM; + + call_usermodehelper_setfns(info, umh_keys_init, umh_keys_cleanup, + key_get(session_keyring)); + return call_usermodehelper_exec(info, wait); +} + +/* + * Request userspace finish the construction of a key + * - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring>" + */ +static int call_sbin_request_key(struct key_construction *cons, + const char *op, + void *aux) +{ + const struct cred *cred = current_cred(); + key_serial_t prkey, sskey; + struct key *key = cons->key, *authkey = cons->authkey, *keyring, + *session; + char *argv[9], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + char desc[20]; + int ret, i; + + kenter("{%d},{%d},%s", key->serial, authkey->serial, op); + + ret = install_user_keyrings(); + if (ret < 0) + goto error_alloc; + + /* allocate a new session keyring */ + sprintf(desc, "_req.%u", key->serial); + + cred = get_current_cred(); + keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + put_cred(cred); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error_alloc; + } + + /* attach the auth key to the session keyring */ + ret = key_link(keyring, authkey); + if (ret < 0) + goto error_link; + + /* record the UID and GID */ + sprintf(uid_str, "%d", cred->fsuid); + sprintf(gid_str, "%d", cred->fsgid); + + /* we say which key is under construction */ + sprintf(key_str, "%d", key->serial); + + /* we specify the process's default keyrings */ + sprintf(keyring_str[0], "%d", + cred->thread_keyring ? cred->thread_keyring->serial : 0); + + prkey = 0; + if (cred->tgcred->process_keyring) + prkey = cred->tgcred->process_keyring->serial; + sprintf(keyring_str[1], "%d", prkey); + + rcu_read_lock(); + session = rcu_dereference(cred->tgcred->session_keyring); + if (!session) + session = cred->user->session_keyring; + sskey = session->serial; + rcu_read_unlock(); + + sprintf(keyring_str[2], "%d", sskey); + + /* set up a minimal environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i] = NULL; + + /* set up the argument list */ + i = 0; + argv[i++] = "/sbin/request-key"; + argv[i++] = (char *) op; + argv[i++] = key_str; + argv[i++] = uid_str; + argv[i++] = gid_str; + argv[i++] = keyring_str[0]; + argv[i++] = keyring_str[1]; + argv[i++] = keyring_str[2]; + argv[i] = NULL; + + /* do it */ + ret = call_usermodehelper_keys(argv[0], argv, envp, keyring, + UMH_WAIT_PROC); + kdebug("usermode -> 0x%x", ret); + if (ret >= 0) { + /* ret is the exit/wait code */ + if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags) || + key_validate(key) < 0) + ret = -ENOKEY; + else + /* ignore any errors from userspace if the key was + * instantiated */ + ret = 0; + } + +error_link: + key_put(keyring); + +error_alloc: + complete_request_key(cons, ret); + kleave(" = %d", ret); + return ret; +} + +/* + * Call out to userspace for key construction. + * + * Program failure is ignored in favour of key status. + */ +static int construct_key(struct key *key, const void *callout_info, + size_t callout_len, void *aux, + struct key *dest_keyring) +{ + struct key_construction *cons; + request_key_actor_t actor; + struct key *authkey; + int ret; + + kenter("%d,%p,%zu,%p", key->serial, callout_info, callout_len, aux); + + cons = kmalloc(sizeof(*cons), GFP_KERNEL); + if (!cons) + return -ENOMEM; + + /* allocate an authorisation key */ + authkey = request_key_auth_new(key, callout_info, callout_len, + dest_keyring); + if (IS_ERR(authkey)) { + kfree(cons); + ret = PTR_ERR(authkey); + authkey = NULL; + } else { + cons->authkey = key_get(authkey); + cons->key = key_get(key); + + /* make the call */ + actor = call_sbin_request_key; + if (key->type->request_key) + actor = key->type->request_key; + + ret = actor(cons, "create", aux); + + /* check that the actor called complete_request_key() prior to + * returning an error */ + WARN_ON(ret < 0 && + !test_bit(KEY_FLAG_REVOKED, &authkey->flags)); + key_put(authkey); + } + + kleave(" = %d", ret); + return ret; +} + +/* + * Get the appropriate destination keyring for the request. + * + * The keyring selected is returned with an extra reference upon it which the + * caller must release. + */ +static void construct_get_dest_keyring(struct key **_dest_keyring) +{ + struct request_key_auth *rka; + const struct cred *cred = current_cred(); + struct key *dest_keyring = *_dest_keyring, *authkey; + + kenter("%p", dest_keyring); + + /* find the appropriate keyring */ + if (dest_keyring) { + /* the caller supplied one */ + key_get(dest_keyring); + } else { + /* use a default keyring; falling through the cases until we + * find one that we actually have */ + switch (cred->jit_keyring) { + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_REQUESTOR_KEYRING: + if (cred->request_key_auth) { + authkey = cred->request_key_auth; + down_read(&authkey->sem); + rka = authkey->payload.data; + if (!test_bit(KEY_FLAG_REVOKED, + &authkey->flags)) + dest_keyring = + key_get(rka->dest_keyring); + up_read(&authkey->sem); + if (dest_keyring) + break; + } + + case KEY_REQKEY_DEFL_THREAD_KEYRING: + dest_keyring = key_get(cred->thread_keyring); + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + dest_keyring = key_get(cred->tgcred->process_keyring); + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_SESSION_KEYRING: + rcu_read_lock(); + dest_keyring = key_get( + rcu_dereference(cred->tgcred->session_keyring)); + rcu_read_unlock(); + + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + dest_keyring = + key_get(cred->user->session_keyring); + break; + + case KEY_REQKEY_DEFL_USER_KEYRING: + dest_keyring = key_get(cred->user->uid_keyring); + break; + + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + BUG(); + } + } + + *_dest_keyring = dest_keyring; + kleave(" [dk %d]", key_serial(dest_keyring)); + return; +} + +/* + * Allocate a new key in under-construction state and attempt to link it in to + * the requested keyring. + * + * May return a key that's already under construction instead if there was a + * race between two thread calling request_key(). + */ +static int construct_alloc_key(struct key_type *type, + const char *description, + struct key *dest_keyring, + unsigned long flags, + struct key_user *user, + struct key **_key) +{ + const struct cred *cred = current_cred(); + unsigned long prealloc; + struct key *key; + key_ref_t key_ref; + int ret; + + kenter("%s,%s,,,", type->name, description); + + *_key = NULL; + mutex_lock(&user->cons_lock); + + key = key_alloc(type, description, cred->fsuid, cred->fsgid, cred, + KEY_POS_ALL, flags); + if (IS_ERR(key)) + goto alloc_failed; + + set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); + + if (dest_keyring) { + ret = __key_link_begin(dest_keyring, type, description, + &prealloc); + if (ret < 0) + goto link_prealloc_failed; + } + + /* attach the key to the destination keyring under lock, but we do need + * to do another check just in case someone beat us to it whilst we + * waited for locks */ + mutex_lock(&key_construction_mutex); + + key_ref = search_process_keyrings(type, description, type->match, cred); + if (!IS_ERR(key_ref)) + goto key_already_present; + + if (dest_keyring) + __key_link(dest_keyring, key, &prealloc); + + mutex_unlock(&key_construction_mutex); + if (dest_keyring) + __key_link_end(dest_keyring, type, prealloc); + mutex_unlock(&user->cons_lock); + *_key = key; + kleave(" = 0 [%d]", key_serial(key)); + return 0; + + /* the key is now present - we tell the caller that we found it by + * returning -EINPROGRESS */ +key_already_present: + key_put(key); + mutex_unlock(&key_construction_mutex); + key = key_ref_to_ptr(key_ref); + if (dest_keyring) { + ret = __key_link_check_live_key(dest_keyring, key); + if (ret == 0) + __key_link(dest_keyring, key, &prealloc); + __key_link_end(dest_keyring, type, prealloc); + if (ret < 0) + goto link_check_failed; + } + mutex_unlock(&user->cons_lock); + *_key = key; + kleave(" = -EINPROGRESS [%d]", key_serial(key)); + return -EINPROGRESS; + +link_check_failed: + mutex_unlock(&user->cons_lock); + key_put(key); + kleave(" = %d [linkcheck]", ret); + return ret; + +link_prealloc_failed: + mutex_unlock(&user->cons_lock); + kleave(" = %d [prelink]", ret); + return ret; + +alloc_failed: + mutex_unlock(&user->cons_lock); + kleave(" = %ld", PTR_ERR(key)); + return PTR_ERR(key); +} + +/* + * Commence key construction. + */ +static struct key *construct_key_and_link(struct key_type *type, + const char *description, + const char *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + struct key_user *user; + struct key *key; + int ret; + + kenter(""); + + user = key_user_lookup(current_fsuid(), current_user_ns()); + if (!user) + return ERR_PTR(-ENOMEM); + + construct_get_dest_keyring(&dest_keyring); + + ret = construct_alloc_key(type, description, dest_keyring, flags, user, + &key); + key_user_put(user); + + if (ret == 0) { + ret = construct_key(key, callout_info, callout_len, aux, + dest_keyring); + if (ret < 0) { + kdebug("cons failed"); + goto construction_failed; + } + } else if (ret == -EINPROGRESS) { + ret = 0; + } else { + goto couldnt_alloc_key; + } + + key_put(dest_keyring); + kleave(" = key %d", key_serial(key)); + return key; + +construction_failed: + key_negate_and_link(key, key_negative_timeout, NULL, NULL); + key_put(key); +couldnt_alloc_key: + key_put(dest_keyring); + kleave(" = %d", ret); + return ERR_PTR(ret); +} + +/** + * request_key_and_link - Request a key and cache it in a keyring. + * @type: The type of key we want. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * @dest_keyring: Where to cache the key. + * @flags: Flags to key_alloc(). + * + * A key matching the specified criteria is searched for in the process's + * keyrings and returned with its usage count incremented if found. Otherwise, + * if callout_info is not NULL, a key will be allocated and some service + * (probably in userspace) will be asked to instantiate it. + * + * If successfully found or created, the key will be linked to the destination + * keyring if one is provided. + * + * Returns a pointer to the key if successful; -EACCES, -ENOKEY, -EKEYREVOKED + * or -EKEYEXPIRED if an inaccessible, negative, revoked or expired key was + * found; -ENOKEY if no key was found and no @callout_info was given; -EDQUOT + * if insufficient key quota was available to create a new key; or -ENOMEM if + * insufficient memory was available. + * + * If the returned key was created, then it may still be under construction, + * and wait_for_key_construction() should be used to wait for that to complete. + */ +struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + const struct cred *cred = current_cred(); + struct key *key; + key_ref_t key_ref; + int ret; + + kenter("%s,%s,%p,%zu,%p,%p,%lx", + type->name, description, callout_info, callout_len, aux, + dest_keyring, flags); + + /* search all the process keyrings for a key */ + key_ref = search_process_keyrings(type, description, type->match, cred); + + if (!IS_ERR(key_ref)) { + key = key_ref_to_ptr(key_ref); + if (dest_keyring) { + construct_get_dest_keyring(&dest_keyring); + ret = key_link(dest_keyring, key); + key_put(dest_keyring); + if (ret < 0) { + key_put(key); + key = ERR_PTR(ret); + goto error; + } + } + } else if (PTR_ERR(key_ref) != -EAGAIN) { + key = ERR_CAST(key_ref); + } else { + /* the search failed, but the keyrings were searchable, so we + * should consult userspace if we can */ + key = ERR_PTR(-ENOKEY); + if (!callout_info) + goto error; + + key = construct_key_and_link(type, description, callout_info, + callout_len, aux, dest_keyring, + flags); + } + +error: + kleave(" = %p", key); + return key; +} + +/** + * wait_for_key_construction - Wait for construction of a key to complete + * @key: The key being waited for. + * @intr: Whether to wait interruptibly. + * + * Wait for a key to finish being constructed. + * + * Returns 0 if successful; -ERESTARTSYS if the wait was interrupted; -ENOKEY + * if the key was negated; or -EKEYREVOKED or -EKEYEXPIRED if the key was + * revoked or expired. + */ +int wait_for_key_construction(struct key *key, bool intr) +{ + int ret; + + ret = wait_on_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT, + intr ? key_wait_bit_intr : key_wait_bit, + intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + if (ret < 0) + return ret; + if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) + return key->type_data.reject_error; + return key_validate(key); +} +EXPORT_SYMBOL(wait_for_key_construction); + +/** + * request_key - Request a key and wait for construction + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found, new keys are always allocated in the user's quota, + * the callout_info must be a NUL-terminated string and no auxiliary data can + * be passed. + * + * Furthermore, it then works as wait_for_key_construction() to wait for the + * completion of keys undergoing construction with a non-interruptible wait. + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key *key; + size_t callout_len = 0; + int ret; + + if (callout_info) + callout_len = strlen(callout_info); + key = request_key_and_link(type, description, callout_info, callout_len, + NULL, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key); + +/** + * request_key_with_auxdata - Request a key with auxiliary data for the upcaller + * @type: The type of key we want. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found and new keys are always allocated in the user's quota. + * + * Furthermore, it then works as wait_for_key_construction() to wait for the + * completion of keys undergoing construction with a non-interruptible wait. + */ +struct key *request_key_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + struct key *key; + int ret; + + key = request_key_and_link(type, description, callout_info, callout_len, + aux, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key_with_auxdata); + +/* + * request_key_async - Request a key (allow async construction) + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found, new keys are always allocated in the user's quota and + * no auxiliary data can be passed. + * + * The caller should call wait_for_key_construction() to wait for the + * completion of the returned key if it is still undergoing construction. + */ +struct key *request_key_async(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len) +{ + return request_key_and_link(type, description, callout_info, + callout_len, NULL, NULL, + KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async); + +/* + * request a key with auxiliary data for the upcaller (allow async construction) + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found and new keys are always allocated in the user's quota. + * + * The caller should call wait_for_key_construction() to wait for the + * completion of the returned key if it is still undergoing construction. + */ +struct key *request_key_async_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + return request_key_and_link(type, description, callout_info, + callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async_with_auxdata); diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c new file mode 100644 index 00000000..60d4e3f5 --- /dev/null +++ b/security/keys/request_key_auth.c @@ -0,0 +1,267 @@ +/* Request key authorisation token key definition. + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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. + * + * See Documentation/security/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int request_key_auth_instantiate(struct key *, const void *, size_t); +static void request_key_auth_describe(const struct key *, struct seq_file *); +static void request_key_auth_revoke(struct key *); +static void request_key_auth_destroy(struct key *); +static long request_key_auth_read(const struct key *, char __user *, size_t); + +/* + * The request-key authorisation key type definition. + */ +struct key_type key_type_request_key_auth = { + .name = ".request_key_auth", + .def_datalen = sizeof(struct request_key_auth), + .instantiate = request_key_auth_instantiate, + .describe = request_key_auth_describe, + .revoke = request_key_auth_revoke, + .destroy = request_key_auth_destroy, + .read = request_key_auth_read, +}; + +/* + * Instantiate a request-key authorisation key. + */ +static int request_key_auth_instantiate(struct key *key, + const void *data, + size_t datalen) +{ + key->payload.data = (struct request_key_auth *) data; + return 0; +} + +/* + * Describe an authorisation token. + */ +static void request_key_auth_describe(const struct key *key, + struct seq_file *m) +{ + struct request_key_auth *rka = key->payload.data; + + seq_puts(m, "key:"); + seq_puts(m, key->description); + if (key_is_instantiated(key)) + seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); +} + +/* + * Read the callout_info data (retrieves the callout information). + * - the key's semaphore is read-locked + */ +static long request_key_auth_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + struct request_key_auth *rka = key->payload.data; + size_t datalen; + long ret; + + datalen = rka->callout_len; + ret = datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > datalen) + buflen = datalen; + + if (copy_to_user(buffer, rka->callout_info, buflen) != 0) + ret = -EFAULT; + } + + return ret; +} + +/* + * Handle revocation of an authorisation token key. + * + * Called with the key sem write-locked. + */ +static void request_key_auth_revoke(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->cred) { + put_cred(rka->cred); + rka->cred = NULL; + } +} + +/* + * Destroy an instantiation authorisation token key. + */ +static void request_key_auth_destroy(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->cred) { + put_cred(rka->cred); + rka->cred = NULL; + } + + key_put(rka->target_key); + key_put(rka->dest_keyring); + kfree(rka->callout_info); + kfree(rka); +} + +/* + * Create an authorisation token for /sbin/request-key or whoever to gain + * access to the caller's security data. + */ +struct key *request_key_auth_new(struct key *target, const void *callout_info, + size_t callout_len, struct key *dest_keyring) +{ + struct request_key_auth *rka, *irka; + const struct cred *cred = current->cred; + struct key *authkey = NULL; + char desc[20]; + int ret; + + kenter("%d,", target->serial); + + /* allocate a auth record */ + rka = kmalloc(sizeof(*rka), GFP_KERNEL); + if (!rka) { + kleave(" = -ENOMEM"); + return ERR_PTR(-ENOMEM); + } + rka->callout_info = kmalloc(callout_len, GFP_KERNEL); + if (!rka->callout_info) { + kleave(" = -ENOMEM"); + kfree(rka); + return ERR_PTR(-ENOMEM); + } + + /* see if the calling process is already servicing the key request of + * another process */ + if (cred->request_key_auth) { + /* it is - use that instantiation context here too */ + down_read(&cred->request_key_auth->sem); + + /* if the auth key has been revoked, then the key we're + * servicing is already instantiated */ + if (test_bit(KEY_FLAG_REVOKED, &cred->request_key_auth->flags)) + goto auth_key_revoked; + + irka = cred->request_key_auth->payload.data; + rka->cred = get_cred(irka->cred); + rka->pid = irka->pid; + + up_read(&cred->request_key_auth->sem); + } + else { + /* it isn't - use this process as the context */ + rka->cred = get_cred(cred); + rka->pid = current->pid; + } + + rka->target_key = key_get(target); + rka->dest_keyring = key_get(dest_keyring); + memcpy(rka->callout_info, callout_info, callout_len); + rka->callout_len = callout_len; + + /* allocate the auth key */ + sprintf(desc, "%x", target->serial); + + authkey = key_alloc(&key_type_request_key_auth, desc, + cred->fsuid, cred->fsgid, cred, + KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | + KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error_alloc; + } + + /* construct the auth key */ + ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL); + if (ret < 0) + goto error_inst; + + kleave(" = {%d,%d}", authkey->serial, atomic_read(&authkey->usage)); + return authkey; + +auth_key_revoked: + up_read(&cred->request_key_auth->sem); + kfree(rka->callout_info); + kfree(rka); + kleave("= -EKEYREVOKED"); + return ERR_PTR(-EKEYREVOKED); + +error_inst: + key_revoke(authkey); + key_put(authkey); +error_alloc: + key_put(rka->target_key); + key_put(rka->dest_keyring); + kfree(rka->callout_info); + kfree(rka); + kleave("= %d", ret); + return ERR_PTR(ret); +} + +/* + * See if an authorisation key is associated with a particular key. + */ +static int key_get_instantiation_authkey_match(const struct key *key, + const void *_id) +{ + struct request_key_auth *rka = key->payload.data; + key_serial_t id = (key_serial_t)(unsigned long) _id; + + return rka->target_key->serial == id; +} + +/* + * Search the current process's keyrings for the authorisation key for + * instantiation of a key. + */ +struct key *key_get_instantiation_authkey(key_serial_t target_id) +{ + const struct cred *cred = current_cred(); + struct key *authkey; + key_ref_t authkey_ref; + + authkey_ref = search_process_keyrings( + &key_type_request_key_auth, + (void *) (unsigned long) target_id, + key_get_instantiation_authkey_match, + cred); + + if (IS_ERR(authkey_ref)) { + authkey = ERR_CAST(authkey_ref); + if (authkey == ERR_PTR(-EAGAIN)) + authkey = ERR_PTR(-ENOKEY); + goto error; + } + + authkey = key_ref_to_ptr(authkey_ref); + if (test_bit(KEY_FLAG_REVOKED, &authkey->flags)) { + key_put(authkey); + authkey = ERR_PTR(-EKEYREVOKED); + } + +error: + return authkey; +} diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c new file mode 100644 index 00000000..ee32d181 --- /dev/null +++ b/security/keys/sysctl.c @@ -0,0 +1,65 @@ +/* Key management controls + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/key.h> +#include <linux/sysctl.h> +#include "internal.h" + +static const int zero, one = 1, max = INT_MAX; + +ctl_table key_sysctls[] = { + { + .procname = "maxkeys", + .data = &key_quota_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "maxbytes", + .data = &key_quota_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "root_maxkeys", + .data = &key_quota_root_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "root_maxbytes", + .data = &key_quota_root_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "gc_delay", + .data = &key_gc_delay, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &zero, + .extra2 = (void *) &max, + }, + { } +}; diff --git a/security/keys/trusted.c b/security/keys/trusted.c new file mode 100644 index 00000000..2d5d041f --- /dev/null +++ b/security/keys/trusted.c @@ -0,0 +1,1191 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * David Safford <safford@us.ibm.com> + * + * 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, version 2 of the License. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/string.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <keys/trusted-type.h> +#include <linux/key-type.h> +#include <linux/rcupdate.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <crypto/sha.h> +#include <linux/capability.h> +#include <linux/tpm.h> +#include <linux/tpm_command.h> + +#include "trusted.h" + +static const char hmac_alg[] = "hmac(sha1)"; +static const char hash_alg[] = "sha1"; + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +static struct sdesc *init_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + sdesc->shash.flags = 0x0; + return sdesc; +} + +static int TSS_sha1(const unsigned char *data, unsigned int datalen, + unsigned char *digest) +{ + struct sdesc *sdesc; + int ret; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); + kfree(sdesc); + return ret; +} + +static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, ...) +{ + struct sdesc *sdesc; + va_list argp; + unsigned int dlen; + unsigned char *data; + int ret; + + sdesc = init_sdesc(hmacalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hmac_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_setkey(hmacalg, key, keylen); + if (ret < 0) + goto out; + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (data == NULL) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, digest); +out: + kfree(sdesc); + return ret; +} + +/* + * calculate authorization info fields to send to TPM + */ +static int TSS_authhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, unsigned char *h1, + unsigned char *h2, unsigned char h3, ...) +{ + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned char *data; + unsigned char c; + int ret; + va_list argp; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + c = h3; + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + va_start(argp, h3); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (!data) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (!ret) + ret = TSS_rawhmac(digest, key, keylen, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, h1, + TPM_NONCE_SIZE, h2, 1, &c, 0, 0); +out: + kfree(sdesc); + return ret; +} + +/* + * verify the AUTH1_COMMAND (Seal) result from TPM + */ +static int TSS_checkhmac1(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key, + unsigned int keylen, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce; + unsigned char *continueflag; + unsigned char *authdata; + unsigned char testhmac[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH1_COMMAND) + return -EINVAL; + authdata = buffer + bufsize - SHA1_DIGEST_SIZE; + continueflag = authdata - 1; + enonce = continueflag - TPM_NONCE_SIZE; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, + sizeof result); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, + sizeof ordinal); + if (ret < 0) + goto out; + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (ret < 0) + goto out; + + ret = TSS_rawhmac(testhmac, key, keylen, SHA1_DIGEST_SIZE, paramdigest, + TPM_NONCE_SIZE, enonce, TPM_NONCE_SIZE, ononce, + 1, continueflag, 0, 0); + if (ret < 0) + goto out; + + if (memcmp(testhmac, authdata, SHA1_DIGEST_SIZE)) + ret = -EINVAL; +out: + kfree(sdesc); + return ret; +} + +/* + * verify the AUTH2_COMMAND (unseal) result from TPM + */ +static int TSS_checkhmac2(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key1, + unsigned int keylen1, + const unsigned char *key2, + unsigned int keylen2, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce1; + unsigned char *continueflag1; + unsigned char *authdata1; + unsigned char *enonce2; + unsigned char *continueflag2; + unsigned char *authdata2; + unsigned char testhmac1[SHA1_DIGEST_SIZE]; + unsigned char testhmac2[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH2_COMMAND) + return -EINVAL; + authdata1 = buffer + bufsize - (SHA1_DIGEST_SIZE + 1 + + SHA1_DIGEST_SIZE + SHA1_DIGEST_SIZE); + authdata2 = buffer + bufsize - (SHA1_DIGEST_SIZE); + continueflag1 = authdata1 - 1; + continueflag2 = authdata2 - 1; + enonce1 = continueflag1 - TPM_NONCE_SIZE; + enonce2 = continueflag2 - TPM_NONCE_SIZE; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, + sizeof result); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, + sizeof ordinal); + if (ret < 0) + goto out; + + va_start(argp, keylen2); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (ret < 0) + goto out; + + ret = TSS_rawhmac(testhmac1, key1, keylen1, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce1, + TPM_NONCE_SIZE, ononce, 1, continueflag1, 0, 0); + if (ret < 0) + goto out; + if (memcmp(testhmac1, authdata1, SHA1_DIGEST_SIZE)) { + ret = -EINVAL; + goto out; + } + ret = TSS_rawhmac(testhmac2, key2, keylen2, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce2, + TPM_NONCE_SIZE, ononce, 1, continueflag2, 0, 0); + if (ret < 0) + goto out; + if (memcmp(testhmac2, authdata2, SHA1_DIGEST_SIZE)) + ret = -EINVAL; +out: + kfree(sdesc); + return ret; +} + +/* + * For key specific tpm requests, we will generate and send our + * own TPM command packets using the drivers send function. + */ +static int trusted_tpm_send(const u32 chip_num, unsigned char *cmd, + size_t buflen) +{ + int rc; + + dump_tpm_buf(cmd); + rc = tpm_send(chip_num, cmd, buflen); + dump_tpm_buf(cmd); + if (rc > 0) + /* Can't return positive return codes values to keyctl */ + rc = -EPERM; + return rc; +} + +/* + * get a random value from TPM + */ +static int tpm_get_random(struct tpm_buf *tb, unsigned char *buf, uint32_t len) +{ + int ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_GETRANDOM_SIZE); + store32(tb, TPM_ORD_GETRANDOM); + store32(tb, len); + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, sizeof tb->data); + if (!ret) + memcpy(buf, tb->data + TPM_GETRANDOM_SIZE, len); + return ret; +} + +static int my_get_random(unsigned char *buf, int len) +{ + struct tpm_buf *tb; + int ret; + + tb = kmalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + ret = tpm_get_random(tb, buf, len); + + kfree(tb); + return ret; +} + +/* + * Lock a trusted key, by extending a selected PCR. + * + * Prevents a trusted key that is sealed to PCRs from being accessed. + * This uses the tpm driver's extend function. + */ +static int pcrlock(const int pcrnum) +{ + unsigned char hash[SHA1_DIGEST_SIZE]; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = my_get_random(hash, SHA1_DIGEST_SIZE); + if (ret < 0) + return ret; + return tpm_pcr_extend(TPM_ANY_NUM, pcrnum, hash) ? -EINVAL : 0; +} + +/* + * Create an object specific authorisation protocol (OSAP) session + */ +static int osap(struct tpm_buf *tb, struct osapsess *s, + const unsigned char *key, uint16_t type, uint32_t handle) +{ + unsigned char enonce[TPM_NONCE_SIZE]; + unsigned char ononce[TPM_NONCE_SIZE]; + int ret; + + ret = tpm_get_random(tb, ononce, TPM_NONCE_SIZE); + if (ret < 0) + return ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_OSAP_SIZE); + store32(tb, TPM_ORD_OSAP); + store16(tb, type); + store32(tb, handle); + storebytes(tb, ononce, TPM_NONCE_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + return ret; + + s->handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(s->enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)]), + TPM_NONCE_SIZE); + memcpy(enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t) + + TPM_NONCE_SIZE]), TPM_NONCE_SIZE); + return TSS_rawhmac(s->secret, key, SHA1_DIGEST_SIZE, TPM_NONCE_SIZE, + enonce, TPM_NONCE_SIZE, ononce, 0, 0); +} + +/* + * Create an object independent authorisation protocol (oiap) session + */ +static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) +{ + int ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_OIAP_SIZE); + store32(tb, TPM_ORD_OIAP); + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + return ret; + + *handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(nonce, &tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)], + TPM_NONCE_SIZE); + return 0; +} + +struct tpm_digests { + unsigned char encauth[SHA1_DIGEST_SIZE]; + unsigned char pubauth[SHA1_DIGEST_SIZE]; + unsigned char xorwork[SHA1_DIGEST_SIZE * 2]; + unsigned char xorhash[SHA1_DIGEST_SIZE]; + unsigned char nonceodd[TPM_NONCE_SIZE]; +}; + +/* + * Have the TPM seal(encrypt) the trusted key, possibly based on + * Platform Configuration Registers (PCRs). AUTH1 for sealing key. + */ +static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *data, uint32_t datalen, + unsigned char *blob, uint32_t *bloblen, + const unsigned char *blobauth, + const unsigned char *pcrinfo, uint32_t pcrinfosize) +{ + struct osapsess sess; + struct tpm_digests *td; + unsigned char cont; + uint32_t ordinal; + uint32_t pcrsize; + uint32_t datsize; + int sealinfosize; + int encdatasize; + int storedsize; + int ret; + int i; + + /* alloc some work space for all the hashes */ + td = kmalloc(sizeof *td, GFP_KERNEL); + if (!td) + return -ENOMEM; + + /* get session for sealing key */ + ret = osap(tb, &sess, keyauth, keytype, keyhandle); + if (ret < 0) + goto out; + dump_sess(&sess); + + /* calculate encrypted authorization value */ + memcpy(td->xorwork, sess.secret, SHA1_DIGEST_SIZE); + memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE); + ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); + if (ret < 0) + goto out; + + ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); + if (ret < 0) + goto out; + ordinal = htonl(TPM_ORD_SEAL); + datsize = htonl(datalen); + pcrsize = htonl(pcrinfosize); + cont = 0; + + /* encrypt data authorization key */ + for (i = 0; i < SHA1_DIGEST_SIZE; ++i) + td->encauth[i] = td->xorhash[i] ^ blobauth[i]; + + /* calculate authorization HMAC value */ + if (pcrinfosize == 0) { + /* no pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + sizeof(uint32_t), &datsize, datalen, data, 0, + 0); + } else { + /* pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + pcrinfosize, pcrinfo, sizeof(uint32_t), + &datsize, datalen, data, 0, 0); + } + if (ret < 0) + goto out; + + /* build and send the TPM request packet */ + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_AUTH1_COMMAND); + store32(tb, TPM_SEAL_SIZE + pcrinfosize + datalen); + store32(tb, TPM_ORD_SEAL); + store32(tb, keyhandle); + storebytes(tb, td->encauth, SHA1_DIGEST_SIZE); + store32(tb, pcrinfosize); + storebytes(tb, pcrinfo, pcrinfosize); + store32(tb, datalen); + storebytes(tb, data, datalen); + store32(tb, sess.handle); + storebytes(tb, td->nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, td->pubauth, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + goto out; + + /* calculate the size of the returned Blob */ + sealinfosize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t)); + encdatasize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t) + + sizeof(uint32_t) + sealinfosize); + storedsize = sizeof(uint32_t) + sizeof(uint32_t) + sealinfosize + + sizeof(uint32_t) + encdatasize; + + /* check the HMAC in the response */ + ret = TSS_checkhmac1(tb->data, ordinal, td->nonceodd, sess.secret, + SHA1_DIGEST_SIZE, storedsize, TPM_DATA_OFFSET, 0, + 0); + + /* copy the returned blob to caller */ + if (!ret) { + memcpy(blob, tb->data + TPM_DATA_OFFSET, storedsize); + *bloblen = storedsize; + } +out: + kfree(td); + return ret; +} + +/* + * use the AUTH2_COMMAND form of unseal, to authorize both key and blob + */ +static int tpm_unseal(struct tpm_buf *tb, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *blob, int bloblen, + const unsigned char *blobauth, + unsigned char *data, unsigned int *datalen) +{ + unsigned char nonceodd[TPM_NONCE_SIZE]; + unsigned char enonce1[TPM_NONCE_SIZE]; + unsigned char enonce2[TPM_NONCE_SIZE]; + unsigned char authdata1[SHA1_DIGEST_SIZE]; + unsigned char authdata2[SHA1_DIGEST_SIZE]; + uint32_t authhandle1 = 0; + uint32_t authhandle2 = 0; + unsigned char cont = 0; + uint32_t ordinal; + uint32_t keyhndl; + int ret; + + /* sessions for unsealing key and data */ + ret = oiap(tb, &authhandle1, enonce1); + if (ret < 0) { + pr_info("trusted_key: oiap failed (%d)\n", ret); + return ret; + } + ret = oiap(tb, &authhandle2, enonce2); + if (ret < 0) { + pr_info("trusted_key: oiap failed (%d)\n", ret); + return ret; + } + + ordinal = htonl(TPM_ORD_UNSEAL); + keyhndl = htonl(SRKHANDLE); + ret = tpm_get_random(tb, nonceodd, TPM_NONCE_SIZE); + if (ret < 0) { + pr_info("trusted_key: tpm_get_random failed (%d)\n", ret); + return ret; + } + ret = TSS_authhmac(authdata1, keyauth, TPM_NONCE_SIZE, + enonce1, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + ret = TSS_authhmac(authdata2, blobauth, TPM_NONCE_SIZE, + enonce2, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + + /* build and send TPM request packet */ + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_AUTH2_COMMAND); + store32(tb, TPM_UNSEAL_SIZE + bloblen); + store32(tb, TPM_ORD_UNSEAL); + store32(tb, keyhandle); + storebytes(tb, blob, bloblen); + store32(tb, authhandle1); + storebytes(tb, nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, authdata1, SHA1_DIGEST_SIZE); + store32(tb, authhandle2); + storebytes(tb, nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, authdata2, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) { + pr_info("trusted_key: authhmac failed (%d)\n", ret); + return ret; + } + + *datalen = LOAD32(tb->data, TPM_DATA_OFFSET); + ret = TSS_checkhmac2(tb->data, ordinal, nonceodd, + keyauth, SHA1_DIGEST_SIZE, + blobauth, SHA1_DIGEST_SIZE, + sizeof(uint32_t), TPM_DATA_OFFSET, + *datalen, TPM_DATA_OFFSET + sizeof(uint32_t), 0, + 0); + if (ret < 0) { + pr_info("trusted_key: TSS_checkhmac2 failed (%d)\n", ret); + return ret; + } + memcpy(data, tb->data + TPM_DATA_OFFSET + sizeof(uint32_t), *datalen); + return 0; +} + +/* + * Have the TPM seal(encrypt) the symmetric key + */ +static int key_seal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + struct tpm_buf *tb; + int ret; + + tb = kzalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + /* 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, + p->key, p->key_len + 1, p->blob, &p->blob_len, + o->blobauth, o->pcrinfo, o->pcrinfo_len); + if (ret < 0) + pr_info("trusted_key: srkseal failed (%d)\n", ret); + + kfree(tb); + return ret; +} + +/* + * Have the TPM unseal(decrypt) the symmetric key + */ +static int key_unseal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + struct tpm_buf *tb; + int ret; + + tb = kzalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + o->blobauth, p->key, &p->key_len); + if (ret < 0) + pr_info("trusted_key: srkunseal failed (%d)\n", ret); + else + /* pull migratable flag out of sealed key */ + p->migratable = p->key[--p->key_len]; + + kfree(tb); + return ret; +} + +enum { + Opt_err = -1, + Opt_new, Opt_load, Opt_update, + Opt_keyhandle, Opt_keyauth, Opt_blobauth, + Opt_pcrinfo, Opt_pcrlock, Opt_migratable +}; + +static const match_table_t key_tokens = { + {Opt_new, "new"}, + {Opt_load, "load"}, + {Opt_update, "update"}, + {Opt_keyhandle, "keyhandle=%s"}, + {Opt_keyauth, "keyauth=%s"}, + {Opt_blobauth, "blobauth=%s"}, + {Opt_pcrinfo, "pcrinfo=%s"}, + {Opt_pcrlock, "pcrlock=%s"}, + {Opt_migratable, "migratable=%s"}, + {Opt_err, NULL} +}; + +/* can have zero or more token= options */ +static int getoptions(char *c, struct trusted_key_payload *pay, + struct trusted_key_options *opt) +{ + substring_t args[MAX_OPT_ARGS]; + char *p = c; + int token; + int res; + unsigned long handle; + unsigned long lock; + + while ((p = strsep(&c, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + token = match_token(p, key_tokens, args); + + switch (token) { + case Opt_pcrinfo: + opt->pcrinfo_len = strlen(args[0].from) / 2; + if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) + return -EINVAL; + res = hex2bin(opt->pcrinfo, args[0].from, + opt->pcrinfo_len); + if (res < 0) + return -EINVAL; + break; + case Opt_keyhandle: + res = strict_strtoul(args[0].from, 16, &handle); + if (res < 0) + return -EINVAL; + opt->keytype = SEAL_keytype; + opt->keyhandle = handle; + break; + case Opt_keyauth: + if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) + return -EINVAL; + res = hex2bin(opt->keyauth, args[0].from, + SHA1_DIGEST_SIZE); + if (res < 0) + return -EINVAL; + break; + case Opt_blobauth: + if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) + return -EINVAL; + res = hex2bin(opt->blobauth, args[0].from, + SHA1_DIGEST_SIZE); + if (res < 0) + return -EINVAL; + break; + case Opt_migratable: + if (*args[0].from == '0') + pay->migratable = 0; + else + return -EINVAL; + break; + case Opt_pcrlock: + res = strict_strtoul(args[0].from, 10, &lock); + if (res < 0) + return -EINVAL; + opt->pcrlock = lock; + break; + default: + return -EINVAL; + } + } + return 0; +} + +/* + * datablob_parse - parse the keyctl data and fill in the + * payload and options structures + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + substring_t args[MAX_OPT_ARGS]; + long keylen; + int ret = -EINVAL; + int key_cmd; + char *c; + + /* main command */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + key_cmd = match_token(c, key_tokens, args); + switch (key_cmd) { + case Opt_new: + /* first argument is key size */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + ret = strict_strtol(c, 10, &keylen); + if (ret < 0 || keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE) + return -EINVAL; + p->key_len = keylen; + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_new; + break; + case Opt_load: + /* first argument is sealed blob */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + p->blob_len = strlen(c) / 2; + if (p->blob_len > MAX_BLOB_SIZE) + return -EINVAL; + ret = hex2bin(p->blob, c, p->blob_len); + if (ret < 0) + return -EINVAL; + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_load; + break; + case Opt_update: + /* all arguments are options */ + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_update; + break; + case Opt_err: + return -EINVAL; + break; + } + return ret; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ + struct trusted_key_options *options; + + options = kzalloc(sizeof *options, GFP_KERNEL); + if (options) { + /* set any non-zero defaults */ + options->keytype = SRK_keytype; + options->keyhandle = SRKHANDLE; + } + return options; +} + +static struct trusted_key_payload *trusted_payload_alloc(struct key *key) +{ + struct trusted_key_payload *p = NULL; + int ret; + + ret = key_payload_reserve(key, sizeof *p); + if (ret < 0) + return p; + p = kzalloc(sizeof *p, GFP_KERNEL); + if (p) + p->migratable = 1; /* migratable by default */ + return p; +} + +/* + * trusted_instantiate - create a new trusted key + * + * Unseal an existing trusted blob or, for a new key, get a + * random key, then seal and create a trusted key-type key, + * adding it to the specified keyring. + * + * On success, return 0. Otherwise return errno. + */ +static int trusted_instantiate(struct key *key, const void *data, + size_t datalen) +{ + struct trusted_key_payload *payload = NULL; + struct trusted_key_options *options = NULL; + char *datablob; + int ret = 0; + int key_cmd; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + memcpy(datablob, data, datalen); + datablob[datalen] = '\0'; + + options = trusted_options_alloc(); + if (!options) { + ret = -ENOMEM; + goto out; + } + payload = trusted_payload_alloc(key); + if (!payload) { + ret = -ENOMEM; + goto out; + } + + key_cmd = datablob_parse(datablob, payload, options); + if (key_cmd < 0) { + ret = key_cmd; + goto out; + } + + dump_payload(payload); + dump_options(options); + + switch (key_cmd) { + case Opt_load: + ret = key_unseal(payload, options); + dump_payload(payload); + dump_options(options); + if (ret < 0) + pr_info("trusted_key: key_unseal failed (%d)\n", ret); + break; + case Opt_new: + ret = my_get_random(payload->key, payload->key_len); + if (ret < 0) { + pr_info("trusted_key: key_create failed (%d)\n", ret); + goto out; + } + ret = key_seal(payload, options); + if (ret < 0) + pr_info("trusted_key: key_seal failed (%d)\n", ret); + break; + default: + ret = -EINVAL; + goto out; + } + if (!ret && options->pcrlock) + ret = pcrlock(options->pcrlock); +out: + kfree(datablob); + kfree(options); + if (!ret) + rcu_assign_keypointer(key, payload); + else + kfree(payload); + return ret; +} + +static void trusted_rcu_free(struct rcu_head *rcu) +{ + struct trusted_key_payload *p; + + p = container_of(rcu, struct trusted_key_payload, rcu); + memset(p->key, 0, p->key_len); + kfree(p); +} + +/* + * trusted_update - reseal an existing key with new PCR values + */ +static int trusted_update(struct key *key, const void *data, size_t datalen) +{ + struct trusted_key_payload *p = key->payload.data; + struct trusted_key_payload *new_p; + struct trusted_key_options *new_o; + char *datablob; + int ret = 0; + + if (!p->migratable) + return -EPERM; + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + new_o = trusted_options_alloc(); + if (!new_o) { + ret = -ENOMEM; + goto out; + } + new_p = trusted_payload_alloc(key); + if (!new_p) { + ret = -ENOMEM; + goto out; + } + + memcpy(datablob, data, datalen); + datablob[datalen] = '\0'; + ret = datablob_parse(datablob, new_p, new_o); + if (ret != Opt_update) { + ret = -EINVAL; + kfree(new_p); + goto out; + } + /* copy old key values, and reseal with new pcrs */ + new_p->migratable = p->migratable; + new_p->key_len = p->key_len; + memcpy(new_p->key, p->key, p->key_len); + dump_payload(p); + dump_payload(new_p); + + ret = key_seal(new_p, new_o); + if (ret < 0) { + pr_info("trusted_key: key_seal failed (%d)\n", ret); + kfree(new_p); + goto out; + } + if (new_o->pcrlock) { + ret = pcrlock(new_o->pcrlock); + if (ret < 0) { + pr_info("trusted_key: pcrlock failed (%d)\n", ret); + kfree(new_p); + goto out; + } + } + rcu_assign_keypointer(key, new_p); + call_rcu(&p->rcu, trusted_rcu_free); +out: + kfree(datablob); + kfree(new_o); + return ret; +} + +/* + * trusted_read - copy the sealed blob data to userspace in hex. + * On success, return to userspace the trusted key datablob size. + */ +static long trusted_read(const struct key *key, char __user *buffer, + size_t buflen) +{ + struct trusted_key_payload *p; + char *ascii_buf; + char *bufp; + int i; + + p = rcu_dereference_key(key); + if (!p) + return -EINVAL; + if (!buffer || buflen <= 0) + return 2 * p->blob_len; + ascii_buf = kmalloc(2 * p->blob_len, GFP_KERNEL); + if (!ascii_buf) + return -ENOMEM; + + bufp = ascii_buf; + for (i = 0; i < p->blob_len; i++) + bufp = hex_byte_pack(bufp, p->blob[i]); + if ((copy_to_user(buffer, ascii_buf, 2 * p->blob_len)) != 0) { + kfree(ascii_buf); + return -EFAULT; + } + kfree(ascii_buf); + return 2 * p->blob_len; +} + +/* + * trusted_destroy - before freeing the key, clear the decrypted data + */ +static void trusted_destroy(struct key *key) +{ + struct trusted_key_payload *p = key->payload.data; + + if (!p) + return; + memset(p->key, 0, p->key_len); + kfree(key->payload.data); +} + +struct key_type key_type_trusted = { + .name = "trusted", + .instantiate = trusted_instantiate, + .update = trusted_update, + .match = user_match, + .destroy = trusted_destroy, + .describe = user_describe, + .read = trusted_read, +}; + +EXPORT_SYMBOL_GPL(key_type_trusted); + +static void trusted_shash_release(void) +{ + if (hashalg) + crypto_free_shash(hashalg); + if (hmacalg) + crypto_free_shash(hmacalg); +} + +static int __init trusted_shash_alloc(void) +{ + int ret; + + hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmacalg)) { + pr_info("trusted_key: could not allocate crypto %s\n", + hmac_alg); + return PTR_ERR(hmacalg); + } + + hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hashalg)) { + pr_info("trusted_key: could not allocate crypto %s\n", + hash_alg); + ret = PTR_ERR(hashalg); + goto hashalg_fail; + } + + return 0; + +hashalg_fail: + crypto_free_shash(hmacalg); + return ret; +} + +static int __init init_trusted(void) +{ + int ret; + + ret = trusted_shash_alloc(); + if (ret < 0) + return ret; + ret = register_key_type(&key_type_trusted); + if (ret < 0) + trusted_shash_release(); + return ret; +} + +static void __exit cleanup_trusted(void) +{ + trusted_shash_release(); + unregister_key_type(&key_type_trusted); +} + +late_initcall(init_trusted); +module_exit(cleanup_trusted); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/trusted.h b/security/keys/trusted.h new file mode 100644 index 00000000..3249fbd2 --- /dev/null +++ b/security/keys/trusted.h @@ -0,0 +1,134 @@ +#ifndef __TRUSTED_KEY_H +#define __TRUSTED_KEY_H + +/* implementation specific TPM constants */ +#define MAX_PCRINFO_SIZE 64 +#define MAX_BUF_SIZE 512 +#define TPM_GETRANDOM_SIZE 14 +#define TPM_OSAP_SIZE 36 +#define TPM_OIAP_SIZE 10 +#define TPM_SEAL_SIZE 87 +#define TPM_UNSEAL_SIZE 104 +#define TPM_SIZE_OFFSET 2 +#define TPM_RETURN_OFFSET 6 +#define TPM_DATA_OFFSET 10 + +#define LOAD32(buffer, offset) (ntohl(*(uint32_t *)&buffer[offset])) +#define LOAD32N(buffer, offset) (*(uint32_t *)&buffer[offset]) +#define LOAD16(buffer, offset) (ntohs(*(uint16_t *)&buffer[offset])) + +struct tpm_buf { + int len; + unsigned char data[MAX_BUF_SIZE]; +}; + +#define INIT_BUF(tb) (tb->len = 0) + +struct osapsess { + uint32_t handle; + unsigned char secret[SHA1_DIGEST_SIZE]; + unsigned char enonce[TPM_NONCE_SIZE]; +}; + +/* discrete values, but have to store in uint16_t for TPM use */ +enum { + SEAL_keytype = 1, + SRK_keytype = 4 +}; + +struct trusted_key_options { + uint16_t keytype; + uint32_t keyhandle; + unsigned char keyauth[SHA1_DIGEST_SIZE]; + unsigned char blobauth[SHA1_DIGEST_SIZE]; + uint32_t pcrinfo_len; + unsigned char pcrinfo[MAX_PCRINFO_SIZE]; + int pcrlock; +}; + +#define TPM_DEBUG 0 + +#if TPM_DEBUG +static inline void dump_options(struct trusted_key_options *o) +{ + pr_info("trusted_key: sealing key type %d\n", o->keytype); + pr_info("trusted_key: sealing key handle %0X\n", o->keyhandle); + pr_info("trusted_key: pcrlock %d\n", o->pcrlock); + pr_info("trusted_key: pcrinfo %d\n", o->pcrinfo_len); + print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE, + 16, 1, o->pcrinfo, o->pcrinfo_len, 0); +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ + pr_info("trusted_key: key_len %d\n", p->key_len); + print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE, + 16, 1, p->key, p->key_len, 0); + pr_info("trusted_key: bloblen %d\n", p->blob_len); + print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE, + 16, 1, p->blob, p->blob_len, 0); + pr_info("trusted_key: migratable %d\n", p->migratable); +} + +static inline void dump_sess(struct osapsess *s) +{ + print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE, + 16, 1, &s->handle, 4, 0); + pr_info("trusted-key: secret:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); + pr_info("trusted-key: enonce:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ + int len; + + pr_info("\ntrusted-key: tpm buffer\n"); + len = LOAD32(buf, TPM_SIZE_OFFSET); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); +} +#else +static inline void dump_options(struct trusted_key_options *o) +{ +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ +} + +static inline void dump_sess(struct osapsess *s) +{ +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ +} +#endif + +static inline void store8(struct tpm_buf *buf, const unsigned char value) +{ + buf->data[buf->len++] = value; +} + +static inline void store16(struct tpm_buf *buf, const uint16_t value) +{ + *(uint16_t *) & buf->data[buf->len] = htons(value); + buf->len += sizeof value; +} + +static inline void store32(struct tpm_buf *buf, const uint32_t value) +{ + *(uint32_t *) & buf->data[buf->len] = htonl(value); + buf->len += sizeof value; +} + +static inline void storebytes(struct tpm_buf *buf, const unsigned char *in, + const int len) +{ + memcpy(buf->data + buf->len, in, len); + buf->len += len; +} +#endif diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 00000000..c7660a25 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,228 @@ +/* user_defined.c: user defined key type + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int logon_vet_description(const char *desc); + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_user = { + .name = "user", + .instantiate = user_instantiate, + .update = user_update, + .match = user_match, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +EXPORT_SYMBOL_GPL(key_type_user); + +/* + * This key type is essentially the same as key_type_user, but it does + * not define a .read op. This is suitable for storing username and + * password pairs in the keyring that you do not want to be readable + * from userspace. + */ +struct key_type key_type_logon = { + .name = "logon", + .instantiate = user_instantiate, + .update = user_update, + .match = user_match, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .vet_description = logon_vet_description, +}; +EXPORT_SYMBOL_GPL(key_type_logon); + +/* + * instantiate a user defined key + */ +int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + /* attach the data */ + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + rcu_assign_keypointer(key, upayload); + ret = 0; + +error: + return ret; +} + +EXPORT_SYMBOL_GPL(user_instantiate); + +/* + * update a user defined key + * - the key's semaphore is write-locked + */ +int user_update(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* construct a replacement payload */ + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + + /* check the quota and attach the new data */ + zap = upayload; + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + rcu_assign_keypointer(key, upayload); + key->expiry = 0; + } + + if (zap) + kfree_rcu(zap, rcu); + +error: + return ret; +} + +EXPORT_SYMBOL_GPL(user_update); + +/* + * match users on their name + */ +int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; +} + +EXPORT_SYMBOL_GPL(user_match); + +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +void user_revoke(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + /* clear the quota */ + key_payload_reserve(key, 0); + + if (upayload) { + rcu_assign_keypointer(key, NULL); + kfree_rcu(upayload, rcu); + } +} + +EXPORT_SYMBOL(user_revoke); + +/* + * dispose of the data dangling from the corpse of a user key + */ +void user_destroy(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + kfree(upayload); +} + +EXPORT_SYMBOL_GPL(user_destroy); + +/* + * describe the user key + */ +void user_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); + if (key_is_instantiated(key)) + seq_printf(m, ": %u", key->datalen); +} + +EXPORT_SYMBOL_GPL(user_describe); + +/* + * read the key data + * - the key's semaphore is read-locked + */ +long user_read(const struct key *key, char __user *buffer, size_t buflen) +{ + struct user_key_payload *upayload; + long ret; + + upayload = rcu_dereference_key(key); + ret = upayload->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > upayload->datalen) + buflen = upayload->datalen; + + if (copy_to_user(buffer, upayload->data, buflen) != 0) + ret = -EFAULT; + } + + return ret; +} + +EXPORT_SYMBOL_GPL(user_read); + +/* Vet the description for a "logon" key */ +static int logon_vet_description(const char *desc) +{ + char *p; + + /* require a "qualified" description string */ + p = strchr(desc, ':'); + if (!p) + return -EINVAL; + + /* also reject description with ':' as first char */ + if (p == desc) + return -EINVAL; + + return 0; +} |