diff mbox

[1/2] KEYS: Implement a big key type that can save to tmpfs

Message ID 20130801173854.28023.82045.stgit@warthog.procyon.org.uk (mailing list archive)
State New, archived
Headers show

Commit Message

David Howells Aug. 1, 2013, 5:38 p.m. UTC
Implement a big key type that can save its contents to tmpfs and thus
swapspace when memory is tight.  This is useful for Kerberos ticket caches.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 include/keys/big_key-type.h |   27 ++++++
 include/linux/key.h         |    1 
 security/keys/Kconfig       |   11 +++
 security/keys/Makefile      |    1 
 security/keys/big_key.c     |  181 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 221 insertions(+)
 create mode 100644 include/keys/big_key-type.h
 create mode 100644 security/keys/big_key.c


--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Nico Williams Aug. 2, 2013, 8:49 p.m. UTC | #1
I think this is the wrong design.

There are two problems you're trying to solve:

a) how rpc.gssd finds credentials for processes on behalf of which it's acting

b) how to create tmpfs locations in which to store credentials (which
can be unbounded in size, so storing them in the kernel is silly; also
the apps that use them are all user-land apps, so that too means
there's no point in storing them in the kernel).

Keyrings for the forst problem (or PAGs, or anything similar) is fine:
on upcall to gssd the kernel tells it what it needs to know to find
those credentials.

(b) can be solved in many ways, and the simplest is to have a
filesystem where top-level directories named after UIDs "exist" as
soon as they are referenced and as long as they are non-empty.  You
can use autofs + tmpfs, or a variant of tmpfs for this.

Solving (b) in a way that does not add a new ccache type (though
having a KEYRING: ccache type that means "find the ccache URI in my
keyring" is fine) is important because many of us run multiple
implementations of Kerberos on any given host, and we do it because:

 - vendors are always behind the curve
 - legacy software linked with old versions of libkrb5 and friends
 - third party software

Please review the above.  Thanks,

Nico
--
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nico Williams Aug. 2, 2013, 8:50 p.m. UTC | #2
On Fri, Aug 2, 2013 at 3:49 PM, Nico Williams <nico@cryptonector.com> wrote:
> Solving (b) in a way that does not add a new ccache type (though
> having a KEYRING: ccache type that means "find the ccache URI in my
> keyring" is fine) is important because many of us run multiple
> implementations of Kerberos on any given host, and we do it because:
>
>  - vendors are always behind the curve
>  - legacy software linked with old versions of libkrb5 and friends
>  - third party software

I forgot to add:

 - special needs

Nico
--
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Howells Aug. 8, 2013, 2:46 p.m. UTC | #3
Nico Williams <nico@cryptonector.com> wrote:

> b) how to create tmpfs locations in which to store credentials (which
> can be unbounded in size, so storing them in the kernel is silly;

Ummm...  tmpfs stores them in the kernel too - though it can page them out to
swap.

I have altered my big-key implementation to just store small items in an
internal buffer and big items in a tmpfs file.  This means that small items
will use up _less_ kernel memory if they're in a key because they won't require
the overhead of a dentry struct and an inode struct.

> (b) can be solved in many ways, and the simplest is to have a
> filesystem where top-level directories named after UIDs "exist" as
> soon as they are referenced and as long as they are non-empty.  You
> can use autofs + tmpfs, or a variant of tmpfs for this.

Don't forget to add user namespaces into the mix :-/

David
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nico Williams Aug. 9, 2013, 4:24 p.m. UTC | #4
On Thu, Aug 8, 2013 at 9:46 AM, David Howells <dhowells@redhat.com> wrote:
> Nico Williams <nico@cryptonector.com> wrote:
>
>> b) how to create tmpfs locations in which to store credentials (which
>> can be unbounded in size, so storing them in the kernel is silly;
>
> Ummm...  tmpfs stores them in the kernel too - though it can page them out to
> swap.

Any filesystem uses kernel, doesn't it?  Notionally, however, they
don't.  And notionally tmpfs is not about using memory but about being
non-persistent.  This is important because tmpfs *is* subject to
swapping and that does create security problems (you should want to
encrypt swap).

There's a difference between "notionally not persistent across
reboots" and "notionally stored in the kernel's address space".  The
real question is: why do you want the latter?

Some people have argued (not in this thread, and not recently on this
list) that credentials should be stored in the kernel and not leave it
(i.e., you shouldn't be able to extract them from the kernel).  This
implies a number of things, but one of those is a bad thing: unbounded
kernel credential caches OR LRU/LFU cache eviction.  Some of us rely
on caching to keep latency predictable, so cache eviction could be a
bit rough.  Unbounded kernel credential caches not subject to paging
are a DoS.  Unbounded-but-paged cred caches would be fine, but really,
what's the difference vis-a-vis FILE ccaches in /tmp?  The answer
depends on whether you allow the creds to leave kernel space.

That brings us to: what do we really want?  If we really want users to
not be able to see their tickets' session keys, then we don't need to
store their tickets in kernel land and we don't need to put
krb5_mk_req*() in the kernel either: we need only *wrap* the keys to
that low-level crypto use of those keys happens in the kernel and the
only thing the kernel stores is the wrapping key.  You might even have
a TPM or similar crypto decelerator handle this.

ISTM that a new ccache type whose purpose is to defeat the tmpfs race
conditions of the FILE ccache is a good thing.  I would prefer it to
be *portable*, and it could be, but keyring stuff isn't.  And FILE
ccache remains the lowest common denominator, so there's that too.
And since I've been dealing with some FILE ccache race conditions (see
new thread I'll start next), I think a) I hate FILE ccaches, b) I
don't see how to avoid having them.

> I have altered my big-key implementation to just store small items in an
> internal buffer and big items in a tmpfs file.  This means that small items
> will use up _less_ kernel memory if they're in a key because they won't require
> the overhead of a dentry struct and an inode struct.

Please make sure to have a cache eviction policy...  I don't know what
is "small" when it comes to Kerberos credentials either.  If the
difference between small and large is "lacks / has PAC and/or CAMMAC"
then you'll find that this difference mostly depends on the
environment, and generally your items will be either all "small" or
all large.  Are you building dead code?

>> (b) can be solved in many ways, and the simplest is to have a
>> filesystem where top-level directories named after UIDs "exist" as
>> soon as they are referenced and as long as they are non-empty.  You
>> can use autofs + tmpfs, or a variant of tmpfs for this.
>
> Don't forget to add user namespaces into the mix :-/

Sure; why the ambivalence smiley?

Nico
--
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/keys/big_key-type.h b/include/keys/big_key-type.h
new file mode 100644
index 0000000..6610d46
--- /dev/null
+++ b/include/keys/big_key-type.h
@@ -0,0 +1,27 @@ 
+/* Big 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 License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _KEYS_BIG_KEY_TYPE_H
+#define _KEYS_BIG_KEY_TYPE_H
+
+#include <linux/key-type.h>
+
+extern struct key_type key_type_big_key;
+
+struct key_preparsed_payload;
+
+extern int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep);
+extern void big_key_revoke(struct key *key);
+extern void big_key_destroy(struct key *key);
+extern void big_key_describe(const struct key *big_key, struct seq_file *m);
+extern long big_key_read(const struct key *key, char __user *buffer, size_t buflen);
+
+#endif /* _KEYS_BIG_KEY_TYPE_H */
diff --git a/include/linux/key.h b/include/linux/key.h
index 2417f78..010dbb6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -201,6 +201,7 @@  struct key {
 			unsigned long		value;
 			void __rcu		*rcudata;
 			void			*data;
+			void			*data2[2];
 		} payload;
 		struct assoc_array keys;
 	};
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 15e0dfe..eafb335 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -20,6 +20,17 @@  config KEYS
 
 	  If you are unsure as to whether this is required, answer N.
 
+config BIG_KEYS
+	tristate "Large payload keys"
+	depends on KEYS
+	select TMPFS
+	help
+	  This option provides support for holding large keys within the kernel
+	  (for example Kerberos ticket caches).  The data may be stored out to
+	  swapspace by tmpfs.
+
+	  If you are unsure as to whether this is required, answer N.
+
 config TRUSTED_KEYS
 	tristate "TRUSTED KEYS"
 	depends on KEYS && TCG_TPM
diff --git a/security/keys/Makefile b/security/keys/Makefile
index 504aaa0..c487c77 100644
--- a/security/keys/Makefile
+++ b/security/keys/Makefile
@@ -22,5 +22,6 @@  obj-$(CONFIG_SYSCTL) += sysctl.o
 #
 # Key types
 #
+obj-$(CONFIG_BIG_KEYS) += big_key.o
 obj-$(CONFIG_TRUSTED_KEYS) += trusted.o
 obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
new file mode 100644
index 0000000..bd19afc
--- /dev/null
+++ b/security/keys/big_key.c
@@ -0,0 +1,181 @@ 
+/* Big 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");
+
+/*
+ * 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;
+
+	/* 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_file_setup("", datalen, 0);
+	if (IS_ERR(file))
+		goto err_quota;
+
+	written = kernel_write(file, prep->data, prep->datalen, 0);
+	if (written != datalen) {
+		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);
+	return 0;
+
+err_fput:
+	fput(file);
+err_quota:
+	key_payload_reserve(key, 0);
+error:
+	return ret;
+}
+
+/*
+ * match big_keys on their name
+ */
+int big_key_match(const struct key *key, const void *description)
+{
+	return strcmp(key->description, description) == 0;
+}
+
+/*
+ * 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))
+		vfs_truncate(path, 0);
+}
+
+/*
+ * dispose of the data dangling from the corpse of a big_key key
+ */
+void big_key_destroy(struct key *key)
+{
+	struct path *path = (struct path *)&key->payload.data2;
+	path_put(path);
+	path->mnt = NULL;
+	path->dentry = NULL;
+}
+
+/*
+ * describe the big_key key
+ */
+void big_key_describe(const struct key *key, struct seq_file *m)
+{
+	struct path *path = (struct path *)&key->payload.data2;
+	struct dentry *dentry = path->dentry;
+	struct inode *inode = dentry ? dentry->d_inode : NULL;
+
+	seq_puts(m, key->description);
+
+	if (inode &&
+	    key_is_instantiated(key) &&
+	    !test_bit(KEY_FLAG_REVOKED, &key->flags))
+		seq_printf(m, ": %llu", i_size_read(inode));
+}
+
+/*
+ * 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)
+{
+	struct path *path = (struct path *)&key->payload.data2;
+	struct file *file;
+	loff_t pos, size = i_size_read(path->dentry->d_inode);
+	long ret;
+
+	ret = size;
+	if (buffer && buflen >= size) {
+		file = dentry_open(path, O_RDONLY, current_cred());
+		if (IS_ERR(file))
+			return PTR_ERR(file);
+
+		pos = 0;
+		ret = vfs_read(file, buffer, size, &pos);
+		fput(file);
+		if (ret >= 0 && ret != size)
+			return -EIO;
+	}
+
+	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);