6176c6c
From d7ccdaa17aab12a49f5e9e327b55167c4af26bf8 Mon Sep 17 00:00:00 2001
9cb73b9
From: David Howells <dhowells@redhat.com>
9cb73b9
Date: Fri, 30 Aug 2013 15:37:54 +0100
9cb73b9
Subject: [PATCH 1/2] KEYS: Implement a big key type that can save to tmpfs
9cb73b9
9cb73b9
Implement a big key type that can save its contents to tmpfs and thus
9cb73b9
swapspace when memory is tight.  This is useful for Kerberos ticket caches.
9cb73b9
9cb73b9
Signed-off-by: David Howells <dhowells@redhat.com>
9cb73b9
Tested-by: Simo Sorce <simo@redhat.com>
9cb73b9
---
9cb73b9
 include/keys/big_key-type.h |  25 ++++++
9cb73b9
 include/linux/key.h         |   1 +
9cb73b9
 security/keys/Kconfig       |  11 +++
9cb73b9
 security/keys/Makefile      |   1 +
9cb73b9
 security/keys/big_key.c     | 204 ++++++++++++++++++++++++++++++++++++++++++++
9cb73b9
 5 files changed, 242 insertions(+)
9cb73b9
 create mode 100644 include/keys/big_key-type.h
9cb73b9
 create mode 100644 security/keys/big_key.c
9cb73b9
9cb73b9
diff --git a/include/keys/big_key-type.h b/include/keys/big_key-type.h
9cb73b9
new file mode 100644
9cb73b9
index 0000000..d69bc8a
9cb73b9
--- /dev/null
9cb73b9
+++ b/include/keys/big_key-type.h
9cb73b9
@@ -0,0 +1,25 @@
9cb73b9
+/* Big capacity key type.
9cb73b9
+ *
9cb73b9
+ * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
9cb73b9
+ * Written by David Howells (dhowells@redhat.com)
9cb73b9
+ *
9cb73b9
+ * This program is free software; you can redistribute it and/or
9cb73b9
+ * modify it under the terms of the GNU General Public License
9cb73b9
+ * as published by the Free Software Foundation; either version
9cb73b9
+ * 2 of the License, or (at your option) any later version.
9cb73b9
+ */
9cb73b9
+
9cb73b9
+#ifndef _KEYS_BIG_KEY_TYPE_H
9cb73b9
+#define _KEYS_BIG_KEY_TYPE_H
9cb73b9
+
9cb73b9
+#include <linux/key-type.h>
9cb73b9
+
9cb73b9
+extern struct key_type key_type_big_key;
9cb73b9
+
9cb73b9
+extern int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep);
9cb73b9
+extern void big_key_revoke(struct key *key);
9cb73b9
+extern void big_key_destroy(struct key *key);
9cb73b9
+extern void big_key_describe(const struct key *big_key, struct seq_file *m);
9cb73b9
+extern long big_key_read(const struct key *key, char __user *buffer, size_t buflen);
9cb73b9
+
9cb73b9
+#endif /* _KEYS_BIG_KEY_TYPE_H */
9cb73b9
diff --git a/include/linux/key.h b/include/linux/key.h
9cb73b9
index 2417f78..010dbb6 100644
9cb73b9
--- a/include/linux/key.h
9cb73b9
+++ b/include/linux/key.h
9cb73b9
@@ -201,6 +201,7 @@ struct key {
9cb73b9
 			unsigned long		value;
9cb73b9
 			void __rcu		*rcudata;
9cb73b9
 			void			*data;
9cb73b9
+			void			*data2[2];
9cb73b9
 		} payload;
9cb73b9
 		struct assoc_array keys;
9cb73b9
 	};
9cb73b9
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
9cb73b9
index 15e0dfe..b563622 100644
9cb73b9
--- a/security/keys/Kconfig
9cb73b9
+++ b/security/keys/Kconfig
9cb73b9
@@ -20,6 +20,17 @@ config KEYS
9cb73b9
 
9cb73b9
 	  If you are unsure as to whether this is required, answer N.
9cb73b9
 
9cb73b9
+config BIG_KEYS
9cb73b9
+	tristate "Large payload keys"
9cb73b9
+	depends on KEYS
9cb73b9
+	depends on TMPFS
9cb73b9
+	help
9cb73b9
+	  This option provides support for holding large keys within the kernel
9cb73b9
+	  (for example Kerberos ticket caches).  The data may be stored out to
9cb73b9
+	  swapspace by tmpfs.
9cb73b9
+
9cb73b9
+	  If you are unsure as to whether this is required, answer N.
9cb73b9
+
9cb73b9
 config TRUSTED_KEYS
9cb73b9
 	tristate "TRUSTED KEYS"
9cb73b9
 	depends on KEYS && TCG_TPM
9cb73b9
diff --git a/security/keys/Makefile b/security/keys/Makefile
9cb73b9
index 504aaa0..c487c77 100644
9cb73b9
--- a/security/keys/Makefile
9cb73b9
+++ b/security/keys/Makefile
9cb73b9
@@ -22,5 +22,6 @@ obj-$(CONFIG_SYSCTL) += sysctl.o
9cb73b9
 #
9cb73b9
 # Key types
9cb73b9
 #
9cb73b9
+obj-$(CONFIG_BIG_KEYS) += big_key.o
9cb73b9
 obj-$(CONFIG_TRUSTED_KEYS) += trusted.o
9cb73b9
 obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/
9cb73b9
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
9cb73b9
new file mode 100644
9cb73b9
index 0000000..5f9defc
9cb73b9
--- /dev/null
9cb73b9
+++ b/security/keys/big_key.c
9cb73b9
@@ -0,0 +1,204 @@
9cb73b9
+/* Large capacity key type
9cb73b9
+ *
9cb73b9
+ * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
9cb73b9
+ * Written by David Howells (dhowells@redhat.com)
9cb73b9
+ *
9cb73b9
+ * This program is free software; you can redistribute it and/or
9cb73b9
+ * modify it under the terms of the GNU General Public Licence
9cb73b9
+ * as published by the Free Software Foundation; either version
9cb73b9
+ * 2 of the Licence, or (at your option) any later version.
9cb73b9
+ */
9cb73b9
+
9cb73b9
+#include <linux/module.h>
9cb73b9
+#include <linux/init.h>
9cb73b9
+#include <linux/seq_file.h>
9cb73b9
+#include <linux/file.h>
9cb73b9
+#include <linux/shmem_fs.h>
9cb73b9
+#include <linux/err.h>
9cb73b9
+#include <keys/user-type.h>
9cb73b9
+#include <keys/big_key-type.h>
9cb73b9
+
9cb73b9
+MODULE_LICENSE("GPL");
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * If the data is under this limit, there's no point creating a shm file to
9cb73b9
+ * hold it as the permanently resident metadata for the shmem fs will be at
9cb73b9
+ * least as large as the data.
9cb73b9
+ */
9cb73b9
+#define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry))
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * big_key defined keys take an arbitrary string as the description and an
9cb73b9
+ * arbitrary blob of data as the payload
9cb73b9
+ */
9cb73b9
+struct key_type key_type_big_key = {
9cb73b9
+	.name			= "big_key",
9cb73b9
+	.def_lookup_type	= KEYRING_SEARCH_LOOKUP_DIRECT,
9cb73b9
+	.instantiate		= big_key_instantiate,
9cb73b9
+	.match			= user_match,
9cb73b9
+	.revoke			= big_key_revoke,
9cb73b9
+	.destroy		= big_key_destroy,
9cb73b9
+	.describe		= big_key_describe,
9cb73b9
+	.read			= big_key_read,
9cb73b9
+};
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * Instantiate a big key
9cb73b9
+ */
9cb73b9
+int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
9cb73b9
+{
9cb73b9
+	struct path *path = (struct path *)&key->payload.data2;
9cb73b9
+	struct file *file;
9cb73b9
+	ssize_t written;
9cb73b9
+	size_t datalen = prep->datalen;
9cb73b9
+	int ret;
9cb73b9
+
9cb73b9
+	ret = -EINVAL;
9cb73b9
+	if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data)
9cb73b9
+		goto error;
9cb73b9
+
9cb73b9
+	/* Set an arbitrary quota */
9cb73b9
+	ret = key_payload_reserve(key, 16);
9cb73b9
+	if (ret < 0)
9cb73b9
+		goto error;
9cb73b9
+
9cb73b9
+	key->type_data.x[1] = datalen;
9cb73b9
+
9cb73b9
+	if (datalen > BIG_KEY_FILE_THRESHOLD) {
9cb73b9
+		/* Create a shmem file to store the data in.  This will permit the data
9cb73b9
+		 * to be swapped out if needed.
9cb73b9
+		 *
9cb73b9
+		 * TODO: Encrypt the stored data with a temporary key.
9cb73b9
+		 */
9cb73b9
+		file = shmem_file_setup("", datalen, 0);
9cb73b9
+		if (IS_ERR(file))
9cb73b9
+			goto err_quota;
9cb73b9
+
9cb73b9
+		written = kernel_write(file, prep->data, prep->datalen, 0);
9cb73b9
+		if (written != datalen) {
9cb73b9
+			if (written >= 0)
9cb73b9
+				ret = -ENOMEM;
9cb73b9
+			goto err_fput;
9cb73b9
+		}
9cb73b9
+
9cb73b9
+		/* Pin the mount and dentry to the key so that we can open it again
9cb73b9
+		 * later
9cb73b9
+		 */
9cb73b9
+		*path = file->f_path;
9cb73b9
+		path_get(path);
9cb73b9
+		fput(file);
9cb73b9
+	} else {
9cb73b9
+		/* Just store the data in a buffer */
9cb73b9
+		void *data = kmalloc(datalen, GFP_KERNEL);
9cb73b9
+		if (!data) {
9cb73b9
+			ret = -ENOMEM;
9cb73b9
+			goto err_quota;
9cb73b9
+		}
9cb73b9
+
9cb73b9
+		key->payload.data = memcpy(data, prep->data, prep->datalen);
9cb73b9
+	}
9cb73b9
+	return 0;
9cb73b9
+
9cb73b9
+err_fput:
9cb73b9
+	fput(file);
9cb73b9
+err_quota:
9cb73b9
+	key_payload_reserve(key, 0);
9cb73b9
+error:
9cb73b9
+	return ret;
9cb73b9
+}
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * dispose of the links from a revoked keyring
9cb73b9
+ * - called with the key sem write-locked
9cb73b9
+ */
9cb73b9
+void big_key_revoke(struct key *key)
9cb73b9
+{
9cb73b9
+	struct path *path = (struct path *)&key->payload.data2;
9cb73b9
+
9cb73b9
+	/* clear the quota */
9cb73b9
+	key_payload_reserve(key, 0);
9cb73b9
+	if (key_is_instantiated(key) && key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD)
9cb73b9
+		vfs_truncate(path, 0);
9cb73b9
+}
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * dispose of the data dangling from the corpse of a big_key key
9cb73b9
+ */
9cb73b9
+void big_key_destroy(struct key *key)
9cb73b9
+{
9cb73b9
+	if (key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) {
9cb73b9
+		struct path *path = (struct path *)&key->payload.data2;
9cb73b9
+		path_put(path);
9cb73b9
+		path->mnt = NULL;
9cb73b9
+		path->dentry = NULL;
9cb73b9
+	} else {
9cb73b9
+		kfree(key->payload.data);
9cb73b9
+		key->payload.data = NULL;
9cb73b9
+	}
9cb73b9
+}
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * describe the big_key key
9cb73b9
+ */
9cb73b9
+void big_key_describe(const struct key *key, struct seq_file *m)
9cb73b9
+{
9cb73b9
+	unsigned long datalen = key->type_data.x[1];
9cb73b9
+
9cb73b9
+	seq_puts(m, key->description);
9cb73b9
+
9cb73b9
+	if (key_is_instantiated(key))
9cb73b9
+		seq_printf(m, ": %lu [%s]",
9cb73b9
+			   datalen,
9cb73b9
+			   datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff");
9cb73b9
+}
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * read the key data
9cb73b9
+ * - the key's semaphore is read-locked
9cb73b9
+ */
9cb73b9
+long big_key_read(const struct key *key, char __user *buffer, size_t buflen)
9cb73b9
+{
9cb73b9
+	unsigned long datalen = key->type_data.x[1];
9cb73b9
+	long ret;
9cb73b9
+
9cb73b9
+	if (!buffer || buflen < datalen)
9cb73b9
+		return datalen;
9cb73b9
+
9cb73b9
+	if (datalen > BIG_KEY_FILE_THRESHOLD) {
9cb73b9
+		struct path *path = (struct path *)&key->payload.data2;
9cb73b9
+		struct file *file;
9cb73b9
+		loff_t pos;
9cb73b9
+
9cb73b9
+		file = dentry_open(path, O_RDONLY, current_cred());
9cb73b9
+		if (IS_ERR(file))
9cb73b9
+			return PTR_ERR(file);
9cb73b9
+
9cb73b9
+		pos = 0;
9cb73b9
+		ret = vfs_read(file, buffer, datalen, &pos;;
9cb73b9
+		fput(file);
9cb73b9
+		if (ret >= 0 && ret != datalen)
9cb73b9
+			ret = -EIO;
9cb73b9
+	} else {
9cb73b9
+		ret = datalen;
9cb73b9
+		if (copy_to_user(buffer, key->payload.data, datalen) != 0)
9cb73b9
+			ret = -EFAULT;
9cb73b9
+	}
9cb73b9
+
9cb73b9
+	return ret;
9cb73b9
+}
9cb73b9
+
9cb73b9
+/*
9cb73b9
+ * Module stuff
9cb73b9
+ */
9cb73b9
+static int __init big_key_init(void)
9cb73b9
+{
9cb73b9
+	return register_key_type(&key_type_big_key);
9cb73b9
+}
9cb73b9
+
9cb73b9
+static void __exit big_key_cleanup(void)
9cb73b9
+{
9cb73b9
+	unregister_key_type(&key_type_big_key);
9cb73b9
+}
9cb73b9
+
9cb73b9
+module_init(big_key_init);
9cb73b9
+module_exit(big_key_cleanup);
9cb73b9
-- 
9cb73b9
1.8.3.1
9cb73b9
9cb73b9
a5801d9
From 862e98313b10123fa4352117b0b0c0f5a530cefb Mon Sep 17 00:00:00 2001
9cb73b9
From: David Howells <dhowells@redhat.com>
9cb73b9
Date: Fri, 30 Aug 2013 15:37:54 +0100
9cb73b9
Subject: [PATCH 2/2] KEYS: Add per-user_namespace registers for persistent
9cb73b9
 per-UID kerberos caches
9cb73b9
9cb73b9
Add support for per-user_namespace registers of persistent per-UID kerberos
9cb73b9
caches held within the kernel.
9cb73b9
9cb73b9
This allows the kerberos cache to be retained beyond the life of all a user's
9cb73b9
processes so that the user's cron jobs can work.
9cb73b9
9cb73b9
The kerberos cache is envisioned as a keyring/key tree looking something like:
9cb73b9
9cb73b9
	struct user_namespace
9cb73b9
	  \___ .krb_cache keyring		- The register
9cb73b9
		\___ _krb.0 keyring		- Root's Kerberos cache
9cb73b9
		\___ _krb.5000 keyring		- User 5000's Kerberos cache
9cb73b9
		\___ _krb.5001 keyring		- User 5001's Kerberos cache
9cb73b9
			\___ tkt785 big_key	- A ccache blob
9cb73b9
			\___ tkt12345 big_key	- Another ccache blob
9cb73b9
9cb73b9
Or possibly:
9cb73b9
9cb73b9
	struct user_namespace
9cb73b9
	  \___ .krb_cache keyring		- The register
9cb73b9
		\___ _krb.0 keyring		- Root's Kerberos cache
9cb73b9
		\___ _krb.5000 keyring		- User 5000's Kerberos cache
9cb73b9
		\___ _krb.5001 keyring		- User 5001's Kerberos cache
9cb73b9
			\___ tkt785 keyring	- A ccache
9cb73b9
				\___ krbtgt/REDHAT.COM@REDHAT.COM big_key
9cb73b9
				\___ http/REDHAT.COM@REDHAT.COM user
9cb73b9
				\___ afs/REDHAT.COM@REDHAT.COM user
9cb73b9
				\___ nfs/REDHAT.COM@REDHAT.COM user
9cb73b9
				\___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key
9cb73b9
				\___ http/KERNEL.ORG@KERNEL.ORG big_key
9cb73b9
9cb73b9
What goes into a particular Kerberos cache is entirely up to userspace.  Kernel
9cb73b9
support is limited to giving you the Kerberos cache keyring that you want.
9cb73b9
9cb73b9
The user asks for their Kerberos cache by:
9cb73b9
9cb73b9
	krb_cache = keyctl_get_krbcache(uid, dest_keyring);
9cb73b9
9cb73b9
The uid is -1 or the user's own UID for the user's own cache or the uid of some
9cb73b9
other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
9cb73b9
mess with the cache.
9cb73b9
9cb73b9
The cache returned is a keyring named "_krb.<uid>" that the possessor can read,
9cb73b9
search, clear, invalidate, unlink from and add links to.  Active LSMs get a
9cb73b9
chance to rule on whether the caller is permitted to make a link.
9cb73b9
9cb73b9
Each uid's cache keyring is created when it first accessed and is given a
9cb73b9
timeout that is extended each time this function is called so that the keyring
9cb73b9
goes away after a while.  The timeout is configurable by sysctl but defaults to
9cb73b9
three days.
9cb73b9
9cb73b9
Each user_namespace struct gets a lazily-created keyring that serves as the
9cb73b9
register.  The cache keyrings are added to it.  This means that standard key
9cb73b9
search and garbage collection facilities are available.
9cb73b9
9cb73b9
The user_namespace struct's register goes away when it does and anything left
9cb73b9
in it is then automatically gc'd.
9cb73b9
9cb73b9
Signed-off-by: David Howells <dhowells@redhat.com>
9cb73b9
Tested-by: Simo Sorce <simo@redhat.com>
9cb73b9
cc: Serge E. Hallyn <serge.hallyn@ubuntu.com>
9cb73b9
cc: Eric W. Biederman <ebiederm@xmission.com>
9cb73b9
---
a5801d9
 include/linux/user_namespace.h |   7 ++
a5801d9
 include/uapi/linux/keyctl.h    |   1 +
a5801d9
 kernel/user.c                  |   4 +
a5801d9
 kernel/user_namespace.c        |   6 ++
a5801d9
 security/keys/Kconfig          |  17 +++++
a5801d9
 security/keys/Makefile         |   1 +
a5801d9
 security/keys/compat.c         |   3 +
a5801d9
 security/keys/internal.h       |   9 +++
a5801d9
 security/keys/keyctl.c         |   3 +
a5801d9
 security/keys/persistent.c     | 169 +++++++++++++++++++++++++++++++++++++++++
a5801d9
 security/keys/sysctl.c         |  11 +++
a5801d9
 11 files changed, 231 insertions(+)
a5801d9
 create mode 100644 security/keys/persistent.c
9cb73b9
9cb73b9
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
6176c6c
index 4db2985..bb0639d 100644
9cb73b9
--- a/include/linux/user_namespace.h
9cb73b9
+++ b/include/linux/user_namespace.h
6176c6c
@@ -27,6 +27,13 @@ struct user_namespace {
6176c6c
 	kuid_t			owner;
6176c6c
 	kgid_t			group;
9cb73b9
 	unsigned int		proc_inum;
9cb73b9
+
9cb73b9
+	/* Register of per-UID persistent keyrings for this namespace */
9cb73b9
+#ifdef CONFIG_PERSISTENT_KEYRINGS
9cb73b9
+	struct key		*persistent_keyring_register;
9cb73b9
+	struct rw_semaphore	persistent_keyring_register_sem;
9cb73b9
+#endif
6176c6c
+
9cb73b9
 };
9cb73b9
 
9cb73b9
 extern struct user_namespace init_user_ns;
9cb73b9
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
9cb73b9
index c9b7f4fa..840cb99 100644
9cb73b9
--- a/include/uapi/linux/keyctl.h
9cb73b9
+++ b/include/uapi/linux/keyctl.h
9cb73b9
@@ -56,5 +56,6 @@
9cb73b9
 #define KEYCTL_REJECT			19	/* reject a partially constructed key */
9cb73b9
 #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
9cb73b9
 #define KEYCTL_INVALIDATE		21	/* invalidate a key */
9cb73b9
+#define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
9cb73b9
 
9cb73b9
 #endif /*  _LINUX_KEYCTL_H */
9cb73b9
diff --git a/kernel/user.c b/kernel/user.c
6176c6c
index 5bbb919..a3a0dbf 100644
9cb73b9
--- a/kernel/user.c
9cb73b9
+++ b/kernel/user.c
6176c6c
@@ -51,6 +51,10 @@ struct user_namespace init_user_ns = {
6176c6c
 	.owner = GLOBAL_ROOT_UID,
6176c6c
 	.group = GLOBAL_ROOT_GID,
9cb73b9
 	.proc_inum = PROC_USER_INIT_INO,
9cb73b9
+#ifdef CONFIG_KEYS_KERBEROS_CACHE
9cb73b9
+	.krb_cache_register_sem =
9cb73b9
+	__RWSEM_INITIALIZER(init_user_ns.krb_cache_register_sem),
9cb73b9
+#endif
9cb73b9
 };
9cb73b9
 EXPORT_SYMBOL_GPL(init_user_ns);
9cb73b9
 
9cb73b9
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
6176c6c
index 13fb113..2dbc299 100644
9cb73b9
--- a/kernel/user_namespace.c
9cb73b9
+++ b/kernel/user_namespace.c
6176c6c
@@ -101,6 +101,9 @@ int create_user_ns(struct cred *new)
9cb73b9
 
6176c6c
 	set_cred_user_ns(new, ns);
9cb73b9
 
9cb73b9
+#ifdef CONFIG_PERSISTENT_KEYRINGS
9cb73b9
+	rwsem_init(&ns->persistent_keyring_register_sem);
9cb73b9
+#endif
9cb73b9
 	return 0;
9cb73b9
 }
9cb73b9
 
6176c6c
@@ -130,6 +133,9 @@ void free_user_ns(struct user_namespace *ns)
9cb73b9
 
9cb73b9
 	do {
9cb73b9
 		parent = ns->parent;
9cb73b9
+#ifdef CONFIG_PERSISTENT_KEYRINGS
9cb73b9
+		key_put(ns->persistent_keyring_register);
9cb73b9
+#endif
9cb73b9
 		proc_free_inum(ns->proc_inum);
9cb73b9
 		kmem_cache_free(user_ns_cachep, ns);
9cb73b9
 		ns = parent;
9cb73b9
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
9cb73b9
index b563622..53d8748 100644
9cb73b9
--- a/security/keys/Kconfig
9cb73b9
+++ b/security/keys/Kconfig
9cb73b9
@@ -20,6 +20,23 @@ config KEYS
9cb73b9
 
9cb73b9
 	  If you are unsure as to whether this is required, answer N.
9cb73b9
 
9cb73b9
+config PERSISTENT_KEYRINGS
9cb73b9
+	bool "Enable register of persistent per-UID keyrings"
9cb73b9
+	depends on KEYS
9cb73b9
+	help
9cb73b9
+	  This option provides a register of persistent per-UID keyrings,
9cb73b9
+	  primarily aimed at Kerberos key storage.  The keyrings are persistent
9cb73b9
+	  in the sense that they stay around after all processes of that UID
9cb73b9
+	  have exited, not that they survive the machine being rebooted.
9cb73b9
+
9cb73b9
+	  A particular keyring may be accessed by either the user whose keyring
9cb73b9
+	  it is or by a process with administrative privileges.  The active
9cb73b9
+	  LSMs gets to rule on which admin-level processes get to access the
9cb73b9
+	  cache.
9cb73b9
+
9cb73b9
+	  Keyrings are created and added into the register upon demand and get
9cb73b9
+	  removed if they expire (a default timeout is set upon creation).
9cb73b9
+
9cb73b9
 config BIG_KEYS
9cb73b9
 	tristate "Large payload keys"
9cb73b9
 	depends on KEYS
9cb73b9
diff --git a/security/keys/Makefile b/security/keys/Makefile
9cb73b9
index c487c77..dfb3a7b 100644
9cb73b9
--- a/security/keys/Makefile
9cb73b9
+++ b/security/keys/Makefile
9cb73b9
@@ -18,6 +18,7 @@ obj-y := \
9cb73b9
 obj-$(CONFIG_KEYS_COMPAT) += compat.o
9cb73b9
 obj-$(CONFIG_PROC_FS) += proc.o
9cb73b9
 obj-$(CONFIG_SYSCTL) += sysctl.o
9cb73b9
+obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o
9cb73b9
 
9cb73b9
 #
9cb73b9
 # Key types
9cb73b9
diff --git a/security/keys/compat.c b/security/keys/compat.c
9cb73b9
index d65fa7f..bbd32c7 100644
9cb73b9
--- a/security/keys/compat.c
9cb73b9
+++ b/security/keys/compat.c
9cb73b9
@@ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
9cb73b9
 	case KEYCTL_INVALIDATE:
9cb73b9
 		return keyctl_invalidate_key(arg2);
9cb73b9
 
9cb73b9
+	case KEYCTL_GET_PERSISTENT:
9cb73b9
+		return keyctl_get_persistent(arg2, arg3);
9cb73b9
+
9cb73b9
 	default:
9cb73b9
 		return -EOPNOTSUPP;
9cb73b9
 	}
9cb73b9
diff --git a/security/keys/internal.h b/security/keys/internal.h
9cb73b9
index 581c6f6..80b2aac 100644
9cb73b9
--- a/security/keys/internal.h
9cb73b9
+++ b/security/keys/internal.h
9cb73b9
@@ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t);
9cb73b9
 extern long keyctl_instantiate_key_common(key_serial_t,
9cb73b9
 					  const struct iovec *,
9cb73b9
 					  unsigned, size_t, key_serial_t);
9cb73b9
+#ifdef CONFIG_PERSISTENT_KEYRINGS
9cb73b9
+extern long keyctl_get_persistent(uid_t, key_serial_t);
9cb73b9
+extern unsigned persistent_keyring_expiry;
9cb73b9
+#else
9cb73b9
+static inline long keyctl_get_persistent(uid_t uid, key_serial_t destring)
9cb73b9
+{
9cb73b9
+	return -EOPNOTSUPP;
9cb73b9
+}
9cb73b9
+#endif
9cb73b9
 
9cb73b9
 /*
9cb73b9
  * Debugging key validation
9cb73b9
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
9cb73b9
index 33cfd27..cee72ce 100644
9cb73b9
--- a/security/keys/keyctl.c
9cb73b9
+++ b/security/keys/keyctl.c
9cb73b9
@@ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
9cb73b9
 	case KEYCTL_INVALIDATE:
9cb73b9
 		return keyctl_invalidate_key((key_serial_t) arg2);
9cb73b9
 
9cb73b9
+	case KEYCTL_GET_PERSISTENT:
9cb73b9
+		return keyctl_get_persistent((uid_t)arg2, (key_serial_t)arg3);
9cb73b9
+
9cb73b9
 	default:
9cb73b9
 		return -EOPNOTSUPP;
9cb73b9
 	}
a5801d9
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
a5801d9
new file mode 100644
a5801d9
index 0000000..631a022
a5801d9
--- /dev/null
a5801d9
+++ b/security/keys/persistent.c
a5801d9
@@ -0,0 +1,169 @@
a5801d9
+/* General persistent per-UID keyrings register
a5801d9
+ *
a5801d9
+ * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
a5801d9
+ * Written by David Howells (dhowells@redhat.com)
a5801d9
+ *
a5801d9
+ * This program is free software; you can redistribute it and/or
a5801d9
+ * modify it under the terms of the GNU General Public Licence
a5801d9
+ * as published by the Free Software Foundation; either version
a5801d9
+ * 2 of the Licence, or (at your option) any later version.
a5801d9
+ */
a5801d9
+
a5801d9
+#include <linux/user_namespace.h>
a5801d9
+#include "internal.h"
a5801d9
+
a5801d9
+unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
a5801d9
+
a5801d9
+/*
a5801d9
+ * Create the persistent keyring register for the current user namespace.
a5801d9
+ *
a5801d9
+ * Called with the namespace's sem locked for writing.
a5801d9
+ */
a5801d9
+static int key_create_persistent_register(struct user_namespace *ns)
a5801d9
+{
a5801d9
+	struct key *reg = keyring_alloc(".persistent_register",
a5801d9
+					KUIDT_INIT(0), KGIDT_INIT(0),
a5801d9
+					current_cred(),
a5801d9
+					((KEY_POS_ALL & ~KEY_POS_SETATTR) |
a5801d9
+					 KEY_USR_VIEW | KEY_USR_READ),
a5801d9
+					KEY_ALLOC_NOT_IN_QUOTA, NULL);
a5801d9
+	if (IS_ERR(reg))
a5801d9
+		return PTR_ERR(reg);
a5801d9
+
a5801d9
+	ns->persistent_keyring_register = reg;
a5801d9
+	return 0;
a5801d9
+}
a5801d9
+
a5801d9
+/*
a5801d9
+ * Create the persistent keyring for the specified user.
a5801d9
+ *
a5801d9
+ * Called with the namespace's sem locked for writing.
a5801d9
+ */
a5801d9
+static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid,
a5801d9
+				       struct keyring_index_key *index_key)
a5801d9
+{
a5801d9
+	struct key *persistent;
a5801d9
+	key_ref_t reg_ref, persistent_ref;
a5801d9
+
a5801d9
+	if (!ns->persistent_keyring_register) {
a5801d9
+		long err = key_create_persistent_register(ns);
a5801d9
+		if (err < 0)
a5801d9
+			return ERR_PTR(err);
a5801d9
+	} else {
a5801d9
+		reg_ref = make_key_ref(ns->persistent_keyring_register, true);
a5801d9
+		persistent_ref = find_key_to_update(reg_ref, index_key);
a5801d9
+		if (persistent_ref)
a5801d9
+			return persistent_ref;
a5801d9
+	}
a5801d9
+
a5801d9
+	persistent = keyring_alloc(index_key->description,
a5801d9
+				   uid, INVALID_GID, current_cred(),
a5801d9
+				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
a5801d9
+				    KEY_USR_VIEW | KEY_USR_READ),
a5801d9
+				   KEY_ALLOC_NOT_IN_QUOTA,
a5801d9
+				   ns->persistent_keyring_register);
a5801d9
+	if (IS_ERR(persistent))
a5801d9
+		return ERR_CAST(persistent);
a5801d9
+
a5801d9
+	return make_key_ref(persistent, true);
a5801d9
+}
a5801d9
+
a5801d9
+/*
a5801d9
+ * Get the persistent keyring for a specific UID and link it to the nominated
a5801d9
+ * keyring.
a5801d9
+ */
a5801d9
+static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
a5801d9
+			       key_ref_t dest_ref)
a5801d9
+{
a5801d9
+	struct keyring_index_key index_key;
a5801d9
+	struct key *persistent;
a5801d9
+	key_ref_t reg_ref, persistent_ref;
a5801d9
+	char buf[32];
a5801d9
+	long ret;
a5801d9
+
a5801d9
+	/* Look in the register if it exists */
a5801d9
+	index_key.type = &key_type_keyring;
a5801d9
+	index_key.description = buf;
a5801d9
+	index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
a5801d9
+
a5801d9
+	if (ns->persistent_keyring_register) {
a5801d9
+		reg_ref = make_key_ref(ns->persistent_keyring_register, true);
a5801d9
+		down_read(&ns->persistent_keyring_register_sem);
a5801d9
+		persistent_ref = find_key_to_update(reg_ref, &index_key);
a5801d9
+		up_read(&ns->persistent_keyring_register_sem);
a5801d9
+
a5801d9
+		if (persistent_ref)
a5801d9
+			goto found;
a5801d9
+	}
a5801d9
+
a5801d9
+	/* It wasn't in the register, so we'll need to create it.  We might
a5801d9
+	 * also need to create the register.
a5801d9
+	 */
a5801d9
+	down_write(&ns->persistent_keyring_register_sem);
a5801d9
+	persistent_ref = key_create_persistent(ns, uid, &index_key);
a5801d9
+	up_write(&ns->persistent_keyring_register_sem);
a5801d9
+	if (!IS_ERR(persistent_ref))
a5801d9
+		goto found;
a5801d9
+
a5801d9
+	return PTR_ERR(persistent_ref);
a5801d9
+
a5801d9
+found:
a5801d9
+	ret = key_task_permission(persistent_ref, current_cred(), KEY_LINK);
a5801d9
+	if (ret == 0) {
a5801d9
+		persistent = key_ref_to_ptr(persistent_ref);
a5801d9
+		ret = key_link(key_ref_to_ptr(dest_ref), persistent);
a5801d9
+		if (ret == 0) {
a5801d9
+			key_set_timeout(persistent, persistent_keyring_expiry);
a5801d9
+			ret = persistent->serial;		
a5801d9
+		}
a5801d9
+	}
a5801d9
+
a5801d9
+	key_ref_put(persistent_ref);
a5801d9
+	return ret;
a5801d9
+}
a5801d9
+
a5801d9
+/*
a5801d9
+ * Get the persistent keyring for a specific UID and link it to the nominated
a5801d9
+ * keyring.
a5801d9
+ */
a5801d9
+long keyctl_get_persistent(uid_t _uid, key_serial_t destid)
a5801d9
+{
a5801d9
+	struct user_namespace *ns = current_user_ns();
a5801d9
+	key_ref_t dest_ref;
a5801d9
+	kuid_t uid;
a5801d9
+	long ret;
a5801d9
+
a5801d9
+	/* -1 indicates the current user */
a5801d9
+	if (_uid == (uid_t)-1) {
a5801d9
+		uid = current_uid();
a5801d9
+	} else {
a5801d9
+		uid = make_kuid(ns, _uid);
a5801d9
+		if (!uid_valid(uid))
a5801d9
+			return -EINVAL;
a5801d9
+
a5801d9
+		/* You can only see your own persistent cache if you're not
a5801d9
+		 * sufficiently privileged.
a5801d9
+		 */
a5801d9
+		if (uid != current_uid() &&
a5801d9
+		    uid != current_suid() &&
a5801d9
+		    uid != current_euid() &&
a5801d9
+		    uid != current_fsuid() &&
a5801d9
+		    !ns_capable(ns, CAP_SETUID))
a5801d9
+			return -EPERM;
a5801d9
+	}
a5801d9
+
a5801d9
+	/* There must be a destination keyring */
a5801d9
+	dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE);
a5801d9
+	if (IS_ERR(dest_ref))
a5801d9
+		return PTR_ERR(dest_ref);
a5801d9
+	if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
a5801d9
+		ret = -ENOTDIR;
a5801d9
+		goto out_put_dest;
a5801d9
+	}
a5801d9
+
a5801d9
+	ret = key_get_persistent(ns, uid, dest_ref);
a5801d9
+
a5801d9
+out_put_dest:
a5801d9
+	key_ref_put(dest_ref);
a5801d9
+	return ret;
a5801d9
+}
9cb73b9
diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c
9cb73b9
index ee32d18..8c0af08 100644
9cb73b9
--- a/security/keys/sysctl.c
9cb73b9
+++ b/security/keys/sysctl.c
9cb73b9
@@ -61,5 +61,16 @@ ctl_table key_sysctls[] = {
9cb73b9
 		.extra1 = (void *) &zero,
9cb73b9
 		.extra2 = (void *) &max,
9cb73b9
 	},
9cb73b9
+#ifdef CONFIG_PERSISTENT_KEYRINGS
9cb73b9
+	{
9cb73b9
+		.procname = "persistent_keyring_expiry",
9cb73b9
+		.data = &persistent_keyring_expiry,
9cb73b9
+		.maxlen = sizeof(unsigned),
9cb73b9
+		.mode = 0644,
9cb73b9
+		.proc_handler = proc_dointvec_minmax,
9cb73b9
+		.extra1 = (void *) &zero,
9cb73b9
+		.extra2 = (void *) &max,
9cb73b9
+	},
9cb73b9
+#endif
9cb73b9
 	{ }
9cb73b9
 };
9cb73b9
-- 
9cb73b9
1.8.3.1
9cb73b9