diff options
Diffstat (limited to 'security/keys/big_key.c')
| -rw-r--r-- | security/keys/big_key.c | 207 | 
1 files changed, 207 insertions, 0 deletions
diff --git a/security/keys/big_key.c b/security/keys/big_key.c new file mode 100644 index 00000000000..8137b27d641 --- /dev/null +++ b/security/keys/big_key.c @@ -0,0 +1,207 @@ +/* Large capacity key type + * + * Copyright (C) 2013 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/init.h> +#include <linux/seq_file.h> +#include <linux/file.h> +#include <linux/shmem_fs.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <keys/big_key-type.h> + +MODULE_LICENSE("GPL"); + +/* + * If the data is under this limit, there's no point creating a shm file to + * hold it as the permanently resident metadata for the shmem fs will be at + * least as large as the data. + */ +#define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry)) + +/* + * big_key defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_big_key = { +	.name			= "big_key", +	.def_lookup_type	= KEYRING_SEARCH_LOOKUP_DIRECT, +	.instantiate		= big_key_instantiate, +	.match			= user_match, +	.revoke			= big_key_revoke, +	.destroy		= big_key_destroy, +	.describe		= big_key_describe, +	.read			= big_key_read, +}; + +/* + * Instantiate a big key + */ +int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep) +{ +	struct path *path = (struct path *)&key->payload.data2; +	struct file *file; +	ssize_t written; +	size_t datalen = prep->datalen; +	int ret; + +	ret = -EINVAL; +	if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) +		goto error; + +	/* Set an arbitrary quota */ +	ret = key_payload_reserve(key, 16); +	if (ret < 0) +		goto error; + +	key->type_data.x[1] = datalen; + +	if (datalen > BIG_KEY_FILE_THRESHOLD) { +		/* Create a shmem file to store the data in.  This will permit the data +		 * to be swapped out if needed. +		 * +		 * TODO: Encrypt the stored data with a temporary key. +		 */ +		file = shmem_kernel_file_setup("", datalen, 0); +		if (IS_ERR(file)) { +			ret = PTR_ERR(file); +			goto err_quota; +		} + +		written = kernel_write(file, prep->data, prep->datalen, 0); +		if (written != datalen) { +			ret = written; +			if (written >= 0) +				ret = -ENOMEM; +			goto err_fput; +		} + +		/* Pin the mount and dentry to the key so that we can open it again +		 * later +		 */ +		*path = file->f_path; +		path_get(path); +		fput(file); +	} else { +		/* Just store the data in a buffer */ +		void *data = kmalloc(datalen, GFP_KERNEL); +		if (!data) { +			ret = -ENOMEM; +			goto err_quota; +		} + +		key->payload.data = memcpy(data, prep->data, prep->datalen); +	} +	return 0; + +err_fput: +	fput(file); +err_quota: +	key_payload_reserve(key, 0); +error: +	return ret; +} + +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +void big_key_revoke(struct key *key) +{ +	struct path *path = (struct path *)&key->payload.data2; + +	/* clear the quota */ +	key_payload_reserve(key, 0); +	if (key_is_instantiated(key) && key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) +		vfs_truncate(path, 0); +} + +/* + * dispose of the data dangling from the corpse of a big_key key + */ +void big_key_destroy(struct key *key) +{ +	if (key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) { +		struct path *path = (struct path *)&key->payload.data2; +		path_put(path); +		path->mnt = NULL; +		path->dentry = NULL; +	} else { +		kfree(key->payload.data); +		key->payload.data = NULL; +	} +} + +/* + * describe the big_key key + */ +void big_key_describe(const struct key *key, struct seq_file *m) +{ +	unsigned long datalen = key->type_data.x[1]; + +	seq_puts(m, key->description); + +	if (key_is_instantiated(key)) +		seq_printf(m, ": %lu [%s]", +			   datalen, +			   datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff"); +} + +/* + * read the key data + * - the key's semaphore is read-locked + */ +long big_key_read(const struct key *key, char __user *buffer, size_t buflen) +{ +	unsigned long datalen = key->type_data.x[1]; +	long ret; + +	if (!buffer || buflen < datalen) +		return datalen; + +	if (datalen > BIG_KEY_FILE_THRESHOLD) { +		struct path *path = (struct path *)&key->payload.data2; +		struct file *file; +		loff_t pos; + +		file = dentry_open(path, O_RDONLY, current_cred()); +		if (IS_ERR(file)) +			return PTR_ERR(file); + +		pos = 0; +		ret = vfs_read(file, buffer, datalen, &pos); +		fput(file); +		if (ret >= 0 && ret != datalen) +			ret = -EIO; +	} else { +		ret = datalen; +		if (copy_to_user(buffer, key->payload.data, datalen) != 0) +			ret = -EFAULT; +	} + +	return ret; +} + +/* + * Module stuff + */ +static int __init big_key_init(void) +{ +	return register_key_type(&key_type_big_key); +} + +static void __exit big_key_cleanup(void) +{ +	unregister_key_type(&key_type_big_key); +} + +module_init(big_key_init); +module_exit(big_key_cleanup);  | 
