diff mbox

[2/2] KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches

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

Commit Message

David Howells Aug. 1, 2013, 5:39 p.m. UTC
Add support for per-user_namespace registers of persistent per-UID kerberos
caches held within the kernel.

This allows the kerberos cache to be retained beyond the life of all a user's
processes so that the user's cron jobs can work.

The kerberos cache is envisioned as a keyring/key tree looking something like:

	struct user_namespace
	  \___ .krb_cache keyring		- The register
		\___ _krb.0 keyring		- Root's Kerberos cache
		\___ _krb.5000 keyring		- User 5000's Kerberos cache
		\___ _krb.5001 keyring		- User 5001's Kerberos cache
			\___ tkt785 big_key	- A ccache blob
			\___ tkt12345 big_key	- Another ccache blob

Or possibly:

	struct user_namespace
	  \___ .krb_cache keyring		- The register
		\___ _krb.0 keyring		- Root's Kerberos cache
		\___ _krb.5000 keyring		- User 5000's Kerberos cache
		\___ _krb.5001 keyring		- User 5001's Kerberos cache
			\___ tkt785 keyring	- A ccache
				\___ krbtgt/REDHAT.COM@REDHAT.COM big_key
				\___ http/REDHAT.COM@REDHAT.COM user
				\___ afs/REDHAT.COM@REDHAT.COM user
				\___ nfs/REDHAT.COM@REDHAT.COM user
				\___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key
				\___ http/KERNEL.ORG@KERNEL.ORG big_key

What goes into a particular Kerberos cache is entirely up to userspace.  Kernel
support is limited to giving you the Kerberos cache keyring that you want.

The user asks for their Kerberos cache by:

	krb_cache = keyctl_get_krbcache(uid, dest_keyring);

The uid is -1 or the user's own UID for the user's own cache or the uid of some
other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
mess with the cache.

The cache returned is a keyring named "_krb.<uid>" that the possessor can read,
search, clear, invalidate, unlink from and add links to.  SELinux and co. get a
say as to whether this call will succeed as the caller must have LINK
permission on the cache keyring.

Each uid's cache keyring is created when it first accessed and is given a
timeout that is extended each time this function is called so that the keyring
goes away after a while.  The timeout is configurable by sysctl but defaults to
three days.

Each user_namespace struct gets a lazily-created keyring that serves as the
register.  The cache keyrings are added to it.  This means that standard key
search and garbage collection facilities are available.

The user_namespace struct's register goes away when it does and anything left
in it is then automatically gc'd.

Signed-off-by: David Howells <dhowells@redhat.com>
cc: Serge E. Hallyn <serge.hallyn@ubuntu.com>
cc: Eric W. Biederman <ebiederm@xmission.com>
---

 include/linux/user_namespace.h |    6 ++
 include/uapi/linux/keyctl.h    |    1 
 kernel/user.c                  |    4 +
 kernel/user_namespace.c        |    2 +
 security/keys/Kconfig          |   12 ++++
 security/keys/Makefile         |    1 
 security/keys/compat.c         |    3 +
 security/keys/internal.h       |    9 +++
 security/keys/keyctl.c         |    3 +
 security/keys/krbcache.c       |  132 ++++++++++++++++++++++++++++++++++++++++
 security/keys/sysctl.c         |   11 +++
 11 files changed, 184 insertions(+)
 create mode 100644 security/keys/krbcache.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

Daniel Kahn Gillmor Aug. 1, 2013, 5:54 p.m. UTC | #1
On 08/01/2013 01:39 PM, David Howells wrote:
> The uid is -1 or the user's own UID for the user's own cache or the uid of some
> other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
> mess with the cache.

Is the goal here eventually to be able to avoid the upcall to rpc.gssd
entirely?  It seems a little bit roundabout to have the kernel call up
into userspace for the credentials, only to talk to a process which then
calls back into the kernel for something that the kernel has already
well-defined internally.

It seems like a non-privileged user could use this to store arbitrary
data in this keyring as a way of hiding what would otherwise be
filesystem activity or using it for some sort of odd/sneaky IPC
mechanism.  Is this an intentional side effect?

Sorry if these are obvious questions.  feel free to point me to
already-documented answers if they exist.

Thanks for all your work on this!

Regards,

	--dkg
Simo Sorce Aug. 1, 2013, 6:29 p.m. UTC | #2
On Thu, 2013-08-01 at 13:54 -0400, Daniel Kahn Gillmor wrote:
> On 08/01/2013 01:39 PM, David Howells wrote:
> > The uid is -1 or the user's own UID for the user's own cache or the uid of some
> > other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
> > mess with the cache.
> 
> Is the goal here eventually to be able to avoid the upcall to rpc.gssd
> entirely?

No, the kernel does not have a GSSAPI implementation anyway, and you do
not want one in kernel.

>   It seems a little bit roundabout to have the kernel call up
> into userspace for the credentials, only to talk to a process which then
> calls back into the kernel for something that the kernel has already
> well-defined internally.

It's called 'abstraction' :-)
The fact that nfs client is in kernel and that the keys api is in kernel
is basically just a coincidence.

> It seems like a non-privileged user could use this to store arbitrary
> data in this keyring as a way of hiding what would otherwise be
> filesystem activity or using it for some sort of odd/sneaky IPC
> mechanism.  Is this an intentional side effect?

Just as a user can add data into a shm segment ?
Is there any difference ?

> Sorry if these are obvious questions.  feel free to point me to
> already-documented answers if they exist.

There isn't much documentation, but it is certainly good to sort out any
questions so we can add answer to any documentation we will come up
with.

Simo.
Daniel Kahn Gillmor Aug. 1, 2013, 6:55 p.m. UTC | #3
On 08/01/2013 02:29 PM, Simo Sorce wrote:

> It's called 'abstraction' :-)

Good, I like abstraction :)

>> It seems like a non-privileged user could use this to store arbitrary
>> data in this keyring as a way of hiding what would otherwise be
>> filesystem activity or using it for some sort of odd/sneaky IPC
>> mechanism.  Is this an intentional side effect?
> 
> Just as a user can add data into a shm segment ?
> Is there any difference ?

I guess this raises the question from a different perspective: if the
kernel already supports arbitrary shm segments, filesystem locations,
etc, which can be used for storing/passing opaque bytestrings between
different parts of userspace, what advantages do we gain from having
this new specific mechanism in the kernel?  Why couldn't those parts of
userspace just rely on already-existing mechanisms instead of
introducing this new interface?

Again, i'm not trying to say it's a bad idea; there's probably a
big-picture piece of the puzzle that i don't see that makes this all
obvious.  i just want to understand what it is.  Thanks for your
explanations!

Regards,

	--dkg
Simo Sorce Aug. 1, 2013, 7:10 p.m. UTC | #4
On Thu, 2013-08-01 at 14:55 -0400, Daniel Kahn Gillmor wrote:
> On 08/01/2013 02:29 PM, Simo Sorce wrote:
> 
> > It's called 'abstraction' :-)
> 
> Good, I like abstraction :)
> 
> >> It seems like a non-privileged user could use this to store arbitrary
> >> data in this keyring as a way of hiding what would otherwise be
> >> filesystem activity or using it for some sort of odd/sneaky IPC
> >> mechanism.  Is this an intentional side effect?
> > 
> > Just as a user can add data into a shm segment ?
> > Is there any difference ?
> 
> I guess this raises the question from a different perspective: if the
> kernel already supports arbitrary shm segments, filesystem locations,
> etc, which can be used for storing/passing opaque bytestrings between
> different parts of userspace, what advantages do we gain from having
> this new specific mechanism in the kernel?  Why couldn't those parts of
> userspace just rely on already-existing mechanisms instead of
> introducing this new interface?
> 
> Again, i'm not trying to say it's a bad idea; there's probably a
> big-picture piece of the puzzle that i don't see that makes this all
> obvious.  i just want to understand what it is.  Thanks for your
> explanations!

A few things deal with creation of the keyring and access control,
automatic garbage collection and in future the fact this data will not
leak in an intelligible form to disc (once we add the encryption bit to
the big_key type). Avoidance of naming collision and so on.
There is probably something else I am forgetting now.

Simo.
Eric W. Biederman Aug. 1, 2013, 11:09 p.m. UTC | #5
David Howells <dhowells@redhat.com> writes:

> Add support for per-user_namespace registers of persistent per-UID kerberos
> caches held within the kernel.

Out of curiosity is this cache per user namspace because the key lookup
is per user namespace?

Some minor nits below. But I don't see anything particulary scary about
this patch.  Other than seeming to make it easy for root to get my
kerbose tickets.

Eric

> This allows the kerberos cache to be retained beyond the life of all a user's
> processes so that the user's cron jobs can work.
>
> The kerberos cache is envisioned as a keyring/key tree looking something like:
>
> 	struct user_namespace
> 	  \___ .krb_cache keyring		- The register
> 		\___ _krb.0 keyring		- Root's Kerberos cache
> 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> 			\___ tkt785 big_key	- A ccache blob
> 			\___ tkt12345 big_key	- Another ccache blob
>
> Or possibly:
>
> 	struct user_namespace
> 	  \___ .krb_cache keyring		- The register
> 		\___ _krb.0 keyring		- Root's Kerberos cache
> 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> 			\___ tkt785 keyring	- A ccache
> 				\___ krbtgt/REDHAT.COM@REDHAT.COM big_key
> 				\___ http/REDHAT.COM@REDHAT.COM user
> 				\___ afs/REDHAT.COM@REDHAT.COM user
> 				\___ nfs/REDHAT.COM@REDHAT.COM user
> 				\___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key
> 				\___ http/KERNEL.ORG@KERNEL.ORG big_key
>
> What goes into a particular Kerberos cache is entirely up to userspace.  Kernel
> support is limited to giving you the Kerberos cache keyring that you want.
>
> The user asks for their Kerberos cache by:
>
> 	krb_cache = keyctl_get_krbcache(uid, dest_keyring);
>
> The uid is -1 or the user's own UID for the user's own cache or the uid of some
> other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
> mess with the cache.
>
> The cache returned is a keyring named "_krb.<uid>" that the possessor can read,
> search, clear, invalidate, unlink from and add links to.  SELinux and co. get a
> say as to whether this call will succeed as the caller must have LINK
> permission on the cache keyring.

I think it would be more accurate to say you use the existing LSM
security hooks for security keys.

Calling out SELinux in particular just seems odd as there is absolutely
nothing SELinux specific in this patch.

> Each uid's cache keyring is created when it first accessed and is given a
> timeout that is extended each time this function is called so that the keyring
> goes away after a while.  The timeout is configurable by sysctl but defaults to
> three days.
>
> Each user_namespace struct gets a lazily-created keyring that serves as the
> register.  The cache keyrings are added to it.  This means that standard key
> search and garbage collection facilities are available.
>
> The user_namespace struct's register goes away when it does and anything left
> in it is then automatically gc'd.
>
> Signed-off-by: David Howells <dhowells@redhat.com>
> cc: Serge E. Hallyn <serge.hallyn@ubuntu.com>
> cc: Eric W. Biederman <ebiederm@xmission.com>
> ---
>
>  include/linux/user_namespace.h |    6 ++
>  include/uapi/linux/keyctl.h    |    1 
>  kernel/user.c                  |    4 +
>  kernel/user_namespace.c        |    2 +
>  security/keys/Kconfig          |   12 ++++
>  security/keys/Makefile         |    1 
>  security/keys/compat.c         |    3 +
>  security/keys/internal.h       |    9 +++
>  security/keys/keyctl.c         |    3 +
>  security/keys/krbcache.c       |  132 ++++++++++++++++++++++++++++++++++++++++
>  security/keys/sysctl.c         |   11 +++
>  11 files changed, 184 insertions(+)
>  create mode 100644 security/keys/krbcache.c
>
> diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
> index b6b215f..3cce8c7 100644
> --- a/include/linux/user_namespace.h
> +++ b/include/linux/user_namespace.h
> @@ -28,6 +28,12 @@ struct user_namespace {
>  	unsigned int		proc_inum;
>  	bool			may_mount_sysfs;
>  	bool			may_mount_proc;
> +
> +	/* Register of per-UID Kerberos caches for this namespace */
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	struct key		*krb_cache_register;
> +	struct rw_semaphore	krb_cache_register_sem;
> +#endif
>  };
>  
>  extern struct user_namespace init_user_ns;
> diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
> index c9b7f4fa..a37c62b 100644
> --- a/include/uapi/linux/keyctl.h
> +++ b/include/uapi/linux/keyctl.h
> @@ -56,5 +56,6 @@
>  #define KEYCTL_REJECT			19	/* reject a partially constructed key */
>  #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
>  #define KEYCTL_INVALIDATE		21	/* invalidate a key */
> +#define KEYCTL_GET_KRBCACHE		22	/* get a user's kerberos cache keyring */
>  
>  #endif /*  _LINUX_KEYCTL_H */
> diff --git a/kernel/user.c b/kernel/user.c
> index 69b4c3d..6c9e1b9 100644
> --- a/kernel/user.c
> +++ b/kernel/user.c
> @@ -53,6 +53,10 @@ struct user_namespace init_user_ns = {
>  	.proc_inum = PROC_USER_INIT_INO,
>  	.may_mount_sysfs = true,
>  	.may_mount_proc = true,
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	.krb_cache_register_sem =
> +	__RWSEM_INITIALIZER(init_user_ns.krb_cache_register_sem),
> +#endif
>  };
>  EXPORT_SYMBOL_GPL(init_user_ns);
>  
> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> index d8c30db..098d954 100644
> --- a/kernel/user_namespace.c
> +++ b/kernel/user_namespace.c
> @@ -99,6 +99,7 @@ int create_user_ns(struct cred *new)
>  
>  	update_mnt_policy(ns);
>  
> +	rwsem_init(&ns->krb_cache_register_sem);
>  	return 0;
>  }
>  
> @@ -123,6 +124,7 @@ void free_user_ns(struct user_namespace *ns)
>  
>  	do {
>  		parent = ns->parent;
> +		key_put(ns->krb_cache_register);
>  		proc_free_inum(ns->proc_inum);
>  		kmem_cache_free(user_ns_cachep, ns);
>  		ns = parent;
> diff --git a/security/keys/Kconfig b/security/keys/Kconfig
> index eafb335..ee3f5a5 100644
> --- a/security/keys/Kconfig
> +++ b/security/keys/Kconfig
> @@ -20,6 +20,18 @@ config KEYS
>  
>  	  If you are unsure as to whether this is required, answer N.
>  
> +config KEYS_KERBEROS_CACHE
> +	bool "Enable persistent keyring-based kerberos cache"
> +	depends on KEYS
> +	help
> +	  This option provides a register of per-UID kerberos cache keyrings.
> +	  A particular keyring may be accessed by either the user whose keyring
> +	  it is or by a process with administrative privileges.  SELinux gets
> +	  to rule on which admin-level processes get to access the cache.
> +
> +	  Keyrings are created and added into the register upon demand and get
> +	  removed if they expire (a default timeout is set upon creation).
> +
>  config BIG_KEYS
>  	tristate "Large payload keys"
>  	depends on KEYS
> diff --git a/security/keys/Makefile b/security/keys/Makefile
> index c487c77..c168ad6 100644
> --- a/security/keys/Makefile
> +++ b/security/keys/Makefile
> @@ -18,6 +18,7 @@ obj-y := \
>  obj-$(CONFIG_KEYS_COMPAT) += compat.o
>  obj-$(CONFIG_PROC_FS) += proc.o
>  obj-$(CONFIG_SYSCTL) += sysctl.o
> +obj-$(CONFIG_KEYS_KERBEROS_CACHE) += krbcache.o
>  
>  #
>  # Key types
> diff --git a/security/keys/compat.c b/security/keys/compat.c
> index d65fa7f..ead383b 100644
> --- a/security/keys/compat.c
> +++ b/security/keys/compat.c
> @@ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
>  	case KEYCTL_INVALIDATE:
>  		return keyctl_invalidate_key(arg2);
>  
> +	case KEYCTL_GET_KRBCACHE:
> +		return keyctl_get_krbcache(arg2, arg3);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 581c6f6..fa379c6 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t);
>  extern long keyctl_instantiate_key_common(key_serial_t,
>  					  const struct iovec *,
>  					  unsigned, size_t, key_serial_t);
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +extern long keyctl_get_krbcache(uid_t, key_serial_t);
> +extern unsigned krb_cache_expiry;
> +#else
> +static inline long keyctl_get_krbcache(uid_t uid, key_serial_t destring)
> +{
> +	return -EOPNOTSUPP;
> +}
> +#endif
>  
>  /*
>   * Debugging key validation
> diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> index 33cfd27..c4fae05 100644
> --- a/security/keys/keyctl.c
> +++ b/security/keys/keyctl.c
> @@ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
>  	case KEYCTL_INVALIDATE:
>  		return keyctl_invalidate_key((key_serial_t) arg2);
>  
> +	case KEYCTL_GET_KRBCACHE:
> +		return keyctl_get_krbcache((uid_t)arg2, (key_serial_t)arg3);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/krbcache.c b/security/keys/krbcache.c
> new file mode 100644
> index 0000000..4e2aa9c
> --- /dev/null
> +++ b/security/keys/krbcache.c
> @@ -0,0 +1,132 @@
> +/* Kerberos persistent cache register
> + *
> + * 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/user_namespace.h>
> +#include "internal.h"
> +
> +unsigned krb_cache_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
> +
> +/*
> + * Get the Kerberos cache keyring for a specific UID and link it to the
> + * nominated keyring.
> + */
> +long keyctl_get_krbcache(uid_t _uid, key_serial_t destid)
> +{
> +	struct user_namespace *ns = current_user_ns();
> +	struct keyring_index_key index_key;
> +	struct key *krb;
> +	key_ref_t reg_ref, dest_ref, krb_ref;
> +	kuid_t uid;
> +	char buf[24];
> +	long ret;
> +
> +	/* -1 indicates the current user */
> +	if (_uid == (uid_t)-1) {
> +		uid = current_uid();
> +	} else {
> +		uid = make_kuid(ns, _uid);
> +		if (!uid_valid(uid))
> +			return -EINVAL;
> +
> +		/* You can only see your own kerberos cache if you're not
> +		 * sufficiently privileged.
> +		 */
> +		if (uid != current_uid() &&
> +		    uid != current_suid() &&
> +		    uid != current_euid() &&
 > +		    uid != current_fsuid() &&
> +		    !nsown_capable(CAP_SETUID))
You you make this ns_capable(ns, CAP_SETUID);

nsown_capable is the right thing here but I am trying to remove the
function because it makes it too easy to not think about which
user namespace you are in.

> +			return -EPERM;
> +	}
> +
> +	/* There must be a destination keyring */
> +	dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE);
> +	if (IS_ERR(dest_ref))
> +		return PTR_ERR(dest_ref);
> +	if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
> +		ret = -ENOTDIR;
> +		goto out_put_dest;
> +	}
> +
> +	/* Look in the register if it exists */
> +	index_key.type = &key_type_keyring;
> +	index_key.description = buf;
> +	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));

Please don't use the implementation detail __kuid_val.  Please use
from_kuid(&init_user_ns, uid) instead so it is explicitly documented
which user namespace you are using.

> +	if (ns->krb_cache_register) {
> +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> +		down_read(&ns->krb_cache_register_sem);
> +		krb_ref = find_key_to_update(reg_ref, &index_key);
> +		up_read(&ns->krb_cache_register_sem);
> +
> +		if (krb_ref)
> +			goto found;
> +	}
> +
> +	/* It wasn't in the register, so we'll need to create it.  We might
> +	 * also need to create the register.
> +	 */
> +	down_write(&ns->krb_cache_register_sem);
> +
> +	if (!ns->krb_cache_register) {
> +		struct key *reg = 
> +			keyring_alloc(".krb_cache",
> +				      KUIDT_INIT(0), KGIDT_INIT(0),
> +				      current_cred(),
> +				      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> +				       KEY_USR_VIEW | KEY_USR_READ),
> +				      KEY_ALLOC_NOT_IN_QUOTA, NULL);
> +		if (IS_ERR(reg)) {
> +			up_write(&ns->krb_cache_register_sem);
> +			ret = PTR_ERR(reg);
> +			goto out_put_dest;
> +		}
> +
> +		ns->krb_cache_register = reg;
> +	} else {
> +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> +		krb_ref = find_key_to_update(reg_ref, &index_key);
> +		if (krb_ref) {
> +			up_write(&ns->krb_cache_register_sem);
> +			goto found;
> +		}
> +	}
> +
> +	krb = keyring_alloc(buf,
> +			    uid, INVALID_GID, current_cred(),
> +			    ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> +			     KEY_USR_VIEW | KEY_USR_READ),
> +			    KEY_ALLOC_NOT_IN_QUOTA,
> +			    ns->krb_cache_register);
> +	up_write(&ns->krb_cache_register_sem);
> +	if (!IS_ERR(krb)) {
> +		krb_ref = make_key_ref(krb, true);
> +		goto found;
> +	}
> +
> +out_put_krb:
> +	key_ref_to_ptr(krb_ref);
> +out_put_dest:
> +	key_ref_to_ptr(dest_ref);
> +	return ret;
> +
> +found:
> +	ret = key_task_permission(krb_ref, current_cred(), KEY_LINK);
> +	if (ret < 0)
> +		goto out_put_krb;
> +
> +	ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(krb_ref));
> +	if (ret == 0) {
> +		key_set_timeout(key_ref_to_ptr(krb_ref), krb_cache_expiry);
> +		ret = key_ref_to_ptr(krb_ref)->serial;		
> +	}
> +	goto out_put_krb;
> +}
> diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c
> index ee32d18..3af1798 100644
> --- a/security/keys/sysctl.c
> +++ b/security/keys/sysctl.c
> @@ -61,5 +61,16 @@ ctl_table key_sysctls[] = {
>  		.extra1 = (void *) &zero,
>  		.extra2 = (void *) &max,
>  	},
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	{
> +		.procname = "krb_expiry",
> +		.data = &krb_cache_expiry,
> +		.maxlen = sizeof(unsigned),
> +		.mode = 0644,
> +		.proc_handler = proc_dointvec_minmax,
> +		.extra1 = (void *) &zero,
> +		.extra2 = (void *) &max,
> +	},
> +#endif
>  	{ }
>  };
--
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
Jeff Layton Aug. 2, 2013, 1:55 p.m. UTC | #6
On Thu, 01 Aug 2013 18:39:02 +0100
David Howells <dhowells@redhat.com> wrote:

> Add support for per-user_namespace registers of persistent per-UID kerberos
> caches held within the kernel.
> 
> This allows the kerberos cache to be retained beyond the life of all a user's
> processes so that the user's cron jobs can work.
> 
> The kerberos cache is envisioned as a keyring/key tree looking something like:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring		- The register
> 		\___ _krb.0 keyring		- Root's Kerberos cache
> 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> 			\___ tkt785 big_key	- A ccache blob
> 			\___ tkt12345 big_key	- Another ccache blob
> 
> Or possibly:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring		- The register
> 		\___ _krb.0 keyring		- Root's Kerberos cache
> 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> 			\___ tkt785 keyring	- A ccache
> 				\___ krbtgt/REDHAT.COM@REDHAT.COM big_key
> 				\___ http/REDHAT.COM@REDHAT.COM user
> 				\___ afs/REDHAT.COM@REDHAT.COM user
> 				\___ nfs/REDHAT.COM@REDHAT.COM user
> 				\___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key
> 				\___ http/KERNEL.ORG@KERNEL.ORG big_key
> 
> What goes into a particular Kerberos cache is entirely up to userspace.  Kernel
> support is limited to giving you the Kerberos cache keyring that you want.
> 
> The user asks for their Kerberos cache by:
> 
> 	krb_cache = keyctl_get_krbcache(uid, dest_keyring);
> 
> The uid is -1 or the user's own UID for the user's own cache or the uid of some
> other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
> mess with the cache.
> 
> The cache returned is a keyring named "_krb.<uid>" that the possessor can read,
> search, clear, invalidate, unlink from and add links to.  SELinux and co. get a
> say as to whether this call will succeed as the caller must have LINK
> permission on the cache keyring.
> 
> Each uid's cache keyring is created when it first accessed and is given a
> timeout that is extended each time this function is called so that the keyring
> goes away after a while.  The timeout is configurable by sysctl but defaults to
> three days.
> 
> Each user_namespace struct gets a lazily-created keyring that serves as the
> register.  The cache keyrings are added to it.  This means that standard key
> search and garbage collection facilities are available.
> 
> The user_namespace struct's register goes away when it does and anything left
> in it is then automatically gc'd.
> 
> Signed-off-by: David Howells <dhowells@redhat.com>
> cc: Serge E. Hallyn <serge.hallyn@ubuntu.com>
> cc: Eric W. Biederman <ebiederm@xmission.com>
> ---
> 
>  include/linux/user_namespace.h |    6 ++
>  include/uapi/linux/keyctl.h    |    1 
>  kernel/user.c                  |    4 +
>  kernel/user_namespace.c        |    2 +
>  security/keys/Kconfig          |   12 ++++
>  security/keys/Makefile         |    1 
>  security/keys/compat.c         |    3 +
>  security/keys/internal.h       |    9 +++
>  security/keys/keyctl.c         |    3 +
>  security/keys/krbcache.c       |  132 ++++++++++++++++++++++++++++++++++++++++
>  security/keys/sysctl.c         |   11 +++
>  11 files changed, 184 insertions(+)
>  create mode 100644 security/keys/krbcache.c
> 
> diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
> index b6b215f..3cce8c7 100644
> --- a/include/linux/user_namespace.h
> +++ b/include/linux/user_namespace.h
> @@ -28,6 +28,12 @@ struct user_namespace {
>  	unsigned int		proc_inum;
>  	bool			may_mount_sysfs;
>  	bool			may_mount_proc;
> +
> +	/* Register of per-UID Kerberos caches for this namespace */
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	struct key		*krb_cache_register;
> +	struct rw_semaphore	krb_cache_register_sem;
> +#endif
>  };
>  
>  extern struct user_namespace init_user_ns;
> diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
> index c9b7f4fa..a37c62b 100644
> --- a/include/uapi/linux/keyctl.h
> +++ b/include/uapi/linux/keyctl.h
> @@ -56,5 +56,6 @@
>  #define KEYCTL_REJECT			19	/* reject a partially constructed key */
>  #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
>  #define KEYCTL_INVALIDATE		21	/* invalidate a key */
> +#define KEYCTL_GET_KRBCACHE		22	/* get a user's kerberos cache keyring */
>  
>  #endif /*  _LINUX_KEYCTL_H */
> diff --git a/kernel/user.c b/kernel/user.c
> index 69b4c3d..6c9e1b9 100644
> --- a/kernel/user.c
> +++ b/kernel/user.c
> @@ -53,6 +53,10 @@ struct user_namespace init_user_ns = {
>  	.proc_inum = PROC_USER_INIT_INO,
>  	.may_mount_sysfs = true,
>  	.may_mount_proc = true,
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	.krb_cache_register_sem =
> +	__RWSEM_INITIALIZER(init_user_ns.krb_cache_register_sem),
> +#endif
>  };
>  EXPORT_SYMBOL_GPL(init_user_ns);
>  
> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> index d8c30db..098d954 100644
> --- a/kernel/user_namespace.c
> +++ b/kernel/user_namespace.c
> @@ -99,6 +99,7 @@ int create_user_ns(struct cred *new)
>  
>  	update_mnt_policy(ns);
>  
> +	rwsem_init(&ns->krb_cache_register_sem);
>  	return 0;
>  }
>  
> @@ -123,6 +124,7 @@ void free_user_ns(struct user_namespace *ns)
>  
>  	do {
>  		parent = ns->parent;
> +		key_put(ns->krb_cache_register);
>  		proc_free_inum(ns->proc_inum);
>  		kmem_cache_free(user_ns_cachep, ns);
>  		ns = parent;
> diff --git a/security/keys/Kconfig b/security/keys/Kconfig
> index eafb335..ee3f5a5 100644
> --- a/security/keys/Kconfig
> +++ b/security/keys/Kconfig
> @@ -20,6 +20,18 @@ config KEYS
>  
>  	  If you are unsure as to whether this is required, answer N.
>  
> +config KEYS_KERBEROS_CACHE
> +	bool "Enable persistent keyring-based kerberos cache"
> +	depends on KEYS
> +	help
> +	  This option provides a register of per-UID kerberos cache keyrings.
> +	  A particular keyring may be accessed by either the user whose keyring
> +	  it is or by a process with administrative privileges.  SELinux gets
> +	  to rule on which admin-level processes get to access the cache.
> +
> +	  Keyrings are created and added into the register upon demand and get
> +	  removed if they expire (a default timeout is set upon creation).
> +
>  config BIG_KEYS
>  	tristate "Large payload keys"
>  	depends on KEYS
> diff --git a/security/keys/Makefile b/security/keys/Makefile
> index c487c77..c168ad6 100644
> --- a/security/keys/Makefile
> +++ b/security/keys/Makefile
> @@ -18,6 +18,7 @@ obj-y := \
>  obj-$(CONFIG_KEYS_COMPAT) += compat.o
>  obj-$(CONFIG_PROC_FS) += proc.o
>  obj-$(CONFIG_SYSCTL) += sysctl.o
> +obj-$(CONFIG_KEYS_KERBEROS_CACHE) += krbcache.o
>  
>  #
>  # Key types
> diff --git a/security/keys/compat.c b/security/keys/compat.c
> index d65fa7f..ead383b 100644
> --- a/security/keys/compat.c
> +++ b/security/keys/compat.c
> @@ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
>  	case KEYCTL_INVALIDATE:
>  		return keyctl_invalidate_key(arg2);
>  
> +	case KEYCTL_GET_KRBCACHE:
> +		return keyctl_get_krbcache(arg2, arg3);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 581c6f6..fa379c6 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t);
>  extern long keyctl_instantiate_key_common(key_serial_t,
>  					  const struct iovec *,
>  					  unsigned, size_t, key_serial_t);
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +extern long keyctl_get_krbcache(uid_t, key_serial_t);
> +extern unsigned krb_cache_expiry;
> +#else
> +static inline long keyctl_get_krbcache(uid_t uid, key_serial_t destring)
> +{
> +	return -EOPNOTSUPP;
> +}
> +#endif
>  
>  /*
>   * Debugging key validation
> diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> index 33cfd27..c4fae05 100644
> --- a/security/keys/keyctl.c
> +++ b/security/keys/keyctl.c
> @@ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
>  	case KEYCTL_INVALIDATE:
>  		return keyctl_invalidate_key((key_serial_t) arg2);
>  
> +	case KEYCTL_GET_KRBCACHE:
> +		return keyctl_get_krbcache((uid_t)arg2, (key_serial_t)arg3);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/krbcache.c b/security/keys/krbcache.c
> new file mode 100644
> index 0000000..4e2aa9c
> --- /dev/null
> +++ b/security/keys/krbcache.c
> @@ -0,0 +1,132 @@
> +/* Kerberos persistent cache register
> + *
> + * 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/user_namespace.h>
> +#include "internal.h"
> +
> +unsigned krb_cache_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
> +
> +/*
> + * Get the Kerberos cache keyring for a specific UID and link it to the
> + * nominated keyring.
> + */
> +long keyctl_get_krbcache(uid_t _uid, key_serial_t destid)
> +{
> +	struct user_namespace *ns = current_user_ns();
> +	struct keyring_index_key index_key;
> +	struct key *krb;
> +	key_ref_t reg_ref, dest_ref, krb_ref;
> +	kuid_t uid;
> +	char buf[24];
> +	long ret;
> +
> +	/* -1 indicates the current user */
> +	if (_uid == (uid_t)-1) {
> +		uid = current_uid();


Isn't it possible to have a valid uid of (unsigned int)-1? I know that
at least some sites use that for "nobody". Why not just require passing
in the correct UID?

> +	} else {
> +		uid = make_kuid(ns, _uid);
> +		if (!uid_valid(uid))
> +			return -EINVAL;
> +
> +		/* You can only see your own kerberos cache if you're not
> +		 * sufficiently privileged.
> +		 */
> +		if (uid != current_uid() &&
> +		    uid != current_suid() &&
> +		    uid != current_euid() &&
> +		    uid != current_fsuid() &&
> +		    !nsown_capable(CAP_SETUID))
> +			return -EPERM;
> +	}
> +
> +	/* There must be a destination keyring */
> +	dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE);
> +	if (IS_ERR(dest_ref))
> +		return PTR_ERR(dest_ref);
> +	if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
> +		ret = -ENOTDIR;
> +		goto out_put_dest;
> +	}
> +
> +	/* Look in the register if it exists */
> +	index_key.type = &key_type_keyring;
> +	index_key.description = buf;
> +	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));
> +
> +	if (ns->krb_cache_register) {
> +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> +		down_read(&ns->krb_cache_register_sem);
> +		krb_ref = find_key_to_update(reg_ref, &index_key);
> +		up_read(&ns->krb_cache_register_sem);
> +
> +		if (krb_ref)
> +			goto found;
> +	}
> +
> +	/* It wasn't in the register, so we'll need to create it.  We might
> +	 * also need to create the register.
> +	 */
> +	down_write(&ns->krb_cache_register_sem);
> +
> +	if (!ns->krb_cache_register) {
> +		struct key *reg = 
> +			keyring_alloc(".krb_cache",
> +				      KUIDT_INIT(0), KGIDT_INIT(0),
> +				      current_cred(),
> +				      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> +				       KEY_USR_VIEW | KEY_USR_READ),
> +				      KEY_ALLOC_NOT_IN_QUOTA, NULL);
> +		if (IS_ERR(reg)) {
> +			up_write(&ns->krb_cache_register_sem);
> +			ret = PTR_ERR(reg);
> +			goto out_put_dest;
> +		}
> +
> +		ns->krb_cache_register = reg;
> +	} else {
> +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> +		krb_ref = find_key_to_update(reg_ref, &index_key);
> +		if (krb_ref) {
> +			up_write(&ns->krb_cache_register_sem);
> +			goto found;
> +		}
> +	}
> +
> +	krb = keyring_alloc(buf,
> +			    uid, INVALID_GID, current_cred(),
> +			    ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> +			     KEY_USR_VIEW | KEY_USR_READ),
> +			    KEY_ALLOC_NOT_IN_QUOTA,
> +			    ns->krb_cache_register);
> +	up_write(&ns->krb_cache_register_sem);
> +	if (!IS_ERR(krb)) {
> +		krb_ref = make_key_ref(krb, true);
> +		goto found;
> +	}
> +
> +out_put_krb:
> +	key_ref_to_ptr(krb_ref);
> +out_put_dest:
> +	key_ref_to_ptr(dest_ref);
> +	return ret;
> +
> +found:
> +	ret = key_task_permission(krb_ref, current_cred(), KEY_LINK);
> +	if (ret < 0)
> +		goto out_put_krb;
> +
> +	ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(krb_ref));
> +	if (ret == 0) {
> +		key_set_timeout(key_ref_to_ptr(krb_ref), krb_cache_expiry);
> +		ret = key_ref_to_ptr(krb_ref)->serial;		
> +	}
> +	goto out_put_krb;
> +}
> diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c
> index ee32d18..3af1798 100644
> --- a/security/keys/sysctl.c
> +++ b/security/keys/sysctl.c
> @@ -61,5 +61,16 @@ ctl_table key_sysctls[] = {
>  		.extra1 = (void *) &zero,
>  		.extra2 = (void *) &max,
>  	},
> +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> +	{
> +		.procname = "krb_expiry",
> +		.data = &krb_cache_expiry,
> +		.maxlen = sizeof(unsigned),
> +		.mode = 0644,
> +		.proc_handler = proc_dointvec_minmax,
> +		.extra1 = (void *) &zero,
> +		.extra2 = (void *) &max,
> +	},
> +#endif
>  	{ }
>  };
> 
> --
> 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

Looks good overall, but I share Daniel's concerns about making
krb5-specific infrastructure like this. Essentially this is just a
persistent keyring that's associated with a kuid, right? Perhaps this
could be done in such a way that it could be usable for other
applications in the future?
Simo Sorce Aug. 2, 2013, 2:16 p.m. UTC | #7
On Fri, 2013-08-02 at 09:55 -0400, Jeff Layton wrote:
> On Thu, 01 Aug 2013 18:39:02 +0100
> David Howells <dhowells@redhat.com> wrote:
> 
> > Add support for per-user_namespace registers of persistent per-UID kerberos
> > caches held within the kernel.
> > 
> > This allows the kerberos cache to be retained beyond the life of all a user's
> > processes so that the user's cron jobs can work.
> > 
> > The kerberos cache is envisioned as a keyring/key tree looking something like:
> > 
> > 	struct user_namespace
> > 	  \___ .krb_cache keyring		- The register
> > 		\___ _krb.0 keyring		- Root's Kerberos cache
> > 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> > 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> > 			\___ tkt785 big_key	- A ccache blob
> > 			\___ tkt12345 big_key	- Another ccache blob
> > 
> > Or possibly:
> > 
> > 	struct user_namespace
> > 	  \___ .krb_cache keyring		- The register
> > 		\___ _krb.0 keyring		- Root's Kerberos cache
> > 		\___ _krb.5000 keyring		- User 5000's Kerberos cache
> > 		\___ _krb.5001 keyring		- User 5001's Kerberos cache
> > 			\___ tkt785 keyring	- A ccache
> > 				\___ krbtgt/REDHAT.COM@REDHAT.COM big_key
> > 				\___ http/REDHAT.COM@REDHAT.COM user
> > 				\___ afs/REDHAT.COM@REDHAT.COM user
> > 				\___ nfs/REDHAT.COM@REDHAT.COM user
> > 				\___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key
> > 				\___ http/KERNEL.ORG@KERNEL.ORG big_key
> > 
> > What goes into a particular Kerberos cache is entirely up to userspace.  Kernel
> > support is limited to giving you the Kerberos cache keyring that you want.
> > 
> > The user asks for their Kerberos cache by:
> > 
> > 	krb_cache = keyctl_get_krbcache(uid, dest_keyring);
> > 
> > The uid is -1 or the user's own UID for the user's own cache or the uid of some
> > other user's cache (requires CAP_SETUID).  This permits rpc.gssd or whatever to
> > mess with the cache.
> > 
> > The cache returned is a keyring named "_krb.<uid>" that the possessor can read,
> > search, clear, invalidate, unlink from and add links to.  SELinux and co. get a
> > say as to whether this call will succeed as the caller must have LINK
> > permission on the cache keyring.
> > 
> > Each uid's cache keyring is created when it first accessed and is given a
> > timeout that is extended each time this function is called so that the keyring
> > goes away after a while.  The timeout is configurable by sysctl but defaults to
> > three days.
> > 
> > Each user_namespace struct gets a lazily-created keyring that serves as the
> > register.  The cache keyrings are added to it.  This means that standard key
> > search and garbage collection facilities are available.
> > 
> > The user_namespace struct's register goes away when it does and anything left
> > in it is then automatically gc'd.
> > 
> > Signed-off-by: David Howells <dhowells@redhat.com>
> > cc: Serge E. Hallyn <serge.hallyn@ubuntu.com>
> > cc: Eric W. Biederman <ebiederm@xmission.com>
> > ---
> > 
> >  include/linux/user_namespace.h |    6 ++
> >  include/uapi/linux/keyctl.h    |    1 
> >  kernel/user.c                  |    4 +
> >  kernel/user_namespace.c        |    2 +
> >  security/keys/Kconfig          |   12 ++++
> >  security/keys/Makefile         |    1 
> >  security/keys/compat.c         |    3 +
> >  security/keys/internal.h       |    9 +++
> >  security/keys/keyctl.c         |    3 +
> >  security/keys/krbcache.c       |  132 ++++++++++++++++++++++++++++++++++++++++
> >  security/keys/sysctl.c         |   11 +++
> >  11 files changed, 184 insertions(+)
> >  create mode 100644 security/keys/krbcache.c
> > 
> > diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
> > index b6b215f..3cce8c7 100644
> > --- a/include/linux/user_namespace.h
> > +++ b/include/linux/user_namespace.h
> > @@ -28,6 +28,12 @@ struct user_namespace {
> >  	unsigned int		proc_inum;
> >  	bool			may_mount_sysfs;
> >  	bool			may_mount_proc;
> > +
> > +	/* Register of per-UID Kerberos caches for this namespace */
> > +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> > +	struct key		*krb_cache_register;
> > +	struct rw_semaphore	krb_cache_register_sem;
> > +#endif
> >  };
> >  
> >  extern struct user_namespace init_user_ns;
> > diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
> > index c9b7f4fa..a37c62b 100644
> > --- a/include/uapi/linux/keyctl.h
> > +++ b/include/uapi/linux/keyctl.h
> > @@ -56,5 +56,6 @@
> >  #define KEYCTL_REJECT			19	/* reject a partially constructed key */
> >  #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
> >  #define KEYCTL_INVALIDATE		21	/* invalidate a key */
> > +#define KEYCTL_GET_KRBCACHE		22	/* get a user's kerberos cache keyring */
> >  
> >  #endif /*  _LINUX_KEYCTL_H */
> > diff --git a/kernel/user.c b/kernel/user.c
> > index 69b4c3d..6c9e1b9 100644
> > --- a/kernel/user.c
> > +++ b/kernel/user.c
> > @@ -53,6 +53,10 @@ struct user_namespace init_user_ns = {
> >  	.proc_inum = PROC_USER_INIT_INO,
> >  	.may_mount_sysfs = true,
> >  	.may_mount_proc = true,
> > +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> > +	.krb_cache_register_sem =
> > +	__RWSEM_INITIALIZER(init_user_ns.krb_cache_register_sem),
> > +#endif
> >  };
> >  EXPORT_SYMBOL_GPL(init_user_ns);
> >  
> > diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> > index d8c30db..098d954 100644
> > --- a/kernel/user_namespace.c
> > +++ b/kernel/user_namespace.c
> > @@ -99,6 +99,7 @@ int create_user_ns(struct cred *new)
> >  
> >  	update_mnt_policy(ns);
> >  
> > +	rwsem_init(&ns->krb_cache_register_sem);
> >  	return 0;
> >  }
> >  
> > @@ -123,6 +124,7 @@ void free_user_ns(struct user_namespace *ns)
> >  
> >  	do {
> >  		parent = ns->parent;
> > +		key_put(ns->krb_cache_register);
> >  		proc_free_inum(ns->proc_inum);
> >  		kmem_cache_free(user_ns_cachep, ns);
> >  		ns = parent;
> > diff --git a/security/keys/Kconfig b/security/keys/Kconfig
> > index eafb335..ee3f5a5 100644
> > --- a/security/keys/Kconfig
> > +++ b/security/keys/Kconfig
> > @@ -20,6 +20,18 @@ config KEYS
> >  
> >  	  If you are unsure as to whether this is required, answer N.
> >  
> > +config KEYS_KERBEROS_CACHE
> > +	bool "Enable persistent keyring-based kerberos cache"
> > +	depends on KEYS
> > +	help
> > +	  This option provides a register of per-UID kerberos cache keyrings.
> > +	  A particular keyring may be accessed by either the user whose keyring
> > +	  it is or by a process with administrative privileges.  SELinux gets
> > +	  to rule on which admin-level processes get to access the cache.
> > +
> > +	  Keyrings are created and added into the register upon demand and get
> > +	  removed if they expire (a default timeout is set upon creation).
> > +
> >  config BIG_KEYS
> >  	tristate "Large payload keys"
> >  	depends on KEYS
> > diff --git a/security/keys/Makefile b/security/keys/Makefile
> > index c487c77..c168ad6 100644
> > --- a/security/keys/Makefile
> > +++ b/security/keys/Makefile
> > @@ -18,6 +18,7 @@ obj-y := \
> >  obj-$(CONFIG_KEYS_COMPAT) += compat.o
> >  obj-$(CONFIG_PROC_FS) += proc.o
> >  obj-$(CONFIG_SYSCTL) += sysctl.o
> > +obj-$(CONFIG_KEYS_KERBEROS_CACHE) += krbcache.o
> >  
> >  #
> >  # Key types
> > diff --git a/security/keys/compat.c b/security/keys/compat.c
> > index d65fa7f..ead383b 100644
> > --- a/security/keys/compat.c
> > +++ b/security/keys/compat.c
> > @@ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
> >  	case KEYCTL_INVALIDATE:
> >  		return keyctl_invalidate_key(arg2);
> >  
> > +	case KEYCTL_GET_KRBCACHE:
> > +		return keyctl_get_krbcache(arg2, arg3);
> > +
> >  	default:
> >  		return -EOPNOTSUPP;
> >  	}
> > diff --git a/security/keys/internal.h b/security/keys/internal.h
> > index 581c6f6..fa379c6 100644
> > --- a/security/keys/internal.h
> > +++ b/security/keys/internal.h
> > @@ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t);
> >  extern long keyctl_instantiate_key_common(key_serial_t,
> >  					  const struct iovec *,
> >  					  unsigned, size_t, key_serial_t);
> > +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> > +extern long keyctl_get_krbcache(uid_t, key_serial_t);
> > +extern unsigned krb_cache_expiry;
> > +#else
> > +static inline long keyctl_get_krbcache(uid_t uid, key_serial_t destring)
> > +{
> > +	return -EOPNOTSUPP;
> > +}
> > +#endif
> >  
> >  /*
> >   * Debugging key validation
> > diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> > index 33cfd27..c4fae05 100644
> > --- a/security/keys/keyctl.c
> > +++ b/security/keys/keyctl.c
> > @@ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
> >  	case KEYCTL_INVALIDATE:
> >  		return keyctl_invalidate_key((key_serial_t) arg2);
> >  
> > +	case KEYCTL_GET_KRBCACHE:
> > +		return keyctl_get_krbcache((uid_t)arg2, (key_serial_t)arg3);
> > +
> >  	default:
> >  		return -EOPNOTSUPP;
> >  	}
> > diff --git a/security/keys/krbcache.c b/security/keys/krbcache.c
> > new file mode 100644
> > index 0000000..4e2aa9c
> > --- /dev/null
> > +++ b/security/keys/krbcache.c
> > @@ -0,0 +1,132 @@
> > +/* Kerberos persistent cache register
> > + *
> > + * 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/user_namespace.h>
> > +#include "internal.h"
> > +
> > +unsigned krb_cache_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
> > +
> > +/*
> > + * Get the Kerberos cache keyring for a specific UID and link it to the
> > + * nominated keyring.
> > + */
> > +long keyctl_get_krbcache(uid_t _uid, key_serial_t destid)
> > +{
> > +	struct user_namespace *ns = current_user_ns();
> > +	struct keyring_index_key index_key;
> > +	struct key *krb;
> > +	key_ref_t reg_ref, dest_ref, krb_ref;
> > +	kuid_t uid;
> > +	char buf[24];
> > +	long ret;
> > +
> > +	/* -1 indicates the current user */
> > +	if (_uid == (uid_t)-1) {
> > +		uid = current_uid();
> 
> 
> Isn't it possible to have a valid uid of (unsigned int)-1? I know that
> at least some sites use that for "nobody". Why not just require passing
> in the correct UID?
> 
> > +	} else {
> > +		uid = make_kuid(ns, _uid);
> > +		if (!uid_valid(uid))
> > +			return -EINVAL;
> > +
> > +		/* You can only see your own kerberos cache if you're not
> > +		 * sufficiently privileged.
> > +		 */
> > +		if (uid != current_uid() &&
> > +		    uid != current_suid() &&
> > +		    uid != current_euid() &&
> > +		    uid != current_fsuid() &&
> > +		    !nsown_capable(CAP_SETUID))
> > +			return -EPERM;
> > +	}
> > +
> > +	/* There must be a destination keyring */
> > +	dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE);
> > +	if (IS_ERR(dest_ref))
> > +		return PTR_ERR(dest_ref);
> > +	if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
> > +		ret = -ENOTDIR;
> > +		goto out_put_dest;
> > +	}
> > +
> > +	/* Look in the register if it exists */
> > +	index_key.type = &key_type_keyring;
> > +	index_key.description = buf;
> > +	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));
> > +
> > +	if (ns->krb_cache_register) {
> > +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> > +		down_read(&ns->krb_cache_register_sem);
> > +		krb_ref = find_key_to_update(reg_ref, &index_key);
> > +		up_read(&ns->krb_cache_register_sem);
> > +
> > +		if (krb_ref)
> > +			goto found;
> > +	}
> > +
> > +	/* It wasn't in the register, so we'll need to create it.  We might
> > +	 * also need to create the register.
> > +	 */
> > +	down_write(&ns->krb_cache_register_sem);
> > +
> > +	if (!ns->krb_cache_register) {
> > +		struct key *reg = 
> > +			keyring_alloc(".krb_cache",
> > +				      KUIDT_INIT(0), KGIDT_INIT(0),
> > +				      current_cred(),
> > +				      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> > +				       KEY_USR_VIEW | KEY_USR_READ),
> > +				      KEY_ALLOC_NOT_IN_QUOTA, NULL);
> > +		if (IS_ERR(reg)) {
> > +			up_write(&ns->krb_cache_register_sem);
> > +			ret = PTR_ERR(reg);
> > +			goto out_put_dest;
> > +		}
> > +
> > +		ns->krb_cache_register = reg;
> > +	} else {
> > +		reg_ref = make_key_ref(ns->krb_cache_register, true);
> > +		krb_ref = find_key_to_update(reg_ref, &index_key);
> > +		if (krb_ref) {
> > +			up_write(&ns->krb_cache_register_sem);
> > +			goto found;
> > +		}
> > +	}
> > +
> > +	krb = keyring_alloc(buf,
> > +			    uid, INVALID_GID, current_cred(),
> > +			    ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
> > +			     KEY_USR_VIEW | KEY_USR_READ),
> > +			    KEY_ALLOC_NOT_IN_QUOTA,
> > +			    ns->krb_cache_register);
> > +	up_write(&ns->krb_cache_register_sem);
> > +	if (!IS_ERR(krb)) {
> > +		krb_ref = make_key_ref(krb, true);
> > +		goto found;
> > +	}
> > +
> > +out_put_krb:
> > +	key_ref_to_ptr(krb_ref);
> > +out_put_dest:
> > +	key_ref_to_ptr(dest_ref);
> > +	return ret;
> > +
> > +found:
> > +	ret = key_task_permission(krb_ref, current_cred(), KEY_LINK);
> > +	if (ret < 0)
> > +		goto out_put_krb;
> > +
> > +	ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(krb_ref));
> > +	if (ret == 0) {
> > +		key_set_timeout(key_ref_to_ptr(krb_ref), krb_cache_expiry);
> > +		ret = key_ref_to_ptr(krb_ref)->serial;		
> > +	}
> > +	goto out_put_krb;
> > +}
> > diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c
> > index ee32d18..3af1798 100644
> > --- a/security/keys/sysctl.c
> > +++ b/security/keys/sysctl.c
> > @@ -61,5 +61,16 @@ ctl_table key_sysctls[] = {
> >  		.extra1 = (void *) &zero,
> >  		.extra2 = (void *) &max,
> >  	},
> > +#ifdef CONFIG_KEYS_KERBEROS_CACHE
> > +	{
> > +		.procname = "krb_expiry",
> > +		.data = &krb_cache_expiry,
> > +		.maxlen = sizeof(unsigned),
> > +		.mode = 0644,
> > +		.proc_handler = proc_dointvec_minmax,
> > +		.extra1 = (void *) &zero,
> > +		.extra2 = (void *) &max,
> > +	},
> > +#endif
> >  	{ }
> >  };
> > 
> > --
> > 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
> 
> Looks good overall, but I share Daniel's concerns about making
> krb5-specific infrastructure like this. Essentially this is just a
> persistent keyring that's associated with a kuid, right? Perhaps this
> could be done in such a way that it could be usable for other
> applications in the future?

Well to be honest there is nothing kerberos specific here but the name
at this stage, however we do not want 'extraneous' keys in the kerberos
keyring. So in order to make it generic we'd have to pass in a prefix at
request time, something like:
keyctl_get_userkeycache(uid, prefix, id);

And then in the kerberos case call it like:
krbcache_id = keyctl_get_userkeycache(uid, "_krb", parent_id);

I see no problem from the kerberos end about using this, but it depends
on whether you want to allow to create arbitrary keyrings like this.

Simo.
David Howells Aug. 2, 2013, 4:53 p.m. UTC | #8
Jeff Layton <jlayton@redhat.com> wrote:

> > +	/* -1 indicates the current user */
> > +	if (_uid == (uid_t)-1) {
> > +		uid = current_uid();
>
> Isn't it possible to have a valid uid of (unsigned int)-1? I know that
> at least some sites use that for "nobody". Why not just require passing
> in the correct UID?

See setresuid() and co. - there -1 is "don't change".

> Looks good overall, but I share Daniel's concerns about making
> krb5-specific infrastructure like this. Essentially this is just a
> persistent keyring that's associated with a kuid, right? Perhaps this
> could be done in such a way that it could be usable for other
> applications in the future?

It's not too hard, I suppose:

	keyctl_get_persistent(uid, prefix, destring)

eg:

	keyctl_get_persistent(-1, "_krb.", KEYCTL_SPEC_PROCESS_KEYRING)

giving:

	struct user_namespace
	  \___ .krb_cache keyring
		\___ _krb.0 keyring
		\___ _krb.5000 keyring
		\___ _krb.5001 keyring
		|	\___ tkt785 big_key
		|	\___ tkt12345 big_key
		\___ _afs.5000 keyring
			\___ afs.redhat.com rxrpc

The other way to do it is create one keyring per user and let userspace create
subkeyrings under that:

	struct user_namespace
	  \___ .krb_cache keyring
		\___ _uid_p.0 keyring
		\___ _uid_p.5000 keyring
		\___ _uid_p.5001 keyring
			\___ krb keyring
			|	\___ tkt785 big_key
			|	\___ tkt12345 big_key
			\___ afs keyring
				\___ afs.redhat.com rxrpc

In the above scheme, it might be worth just making these the same as the user
keyring - which means KEYCTL_SPEC_USER_KEYRING will automatically target it.

Simo:  I believe the problem you have with the user keyring is that it's not
persistent beyond the life of the processes of that UID, right?

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
Simo Sorce Aug. 2, 2013, 5 p.m. UTC | #9
On Fri, 2013-08-02 at 17:53 +0100, David Howells wrote:
> Jeff Layton <jlayton@redhat.com> wrote:
> 
> > > +	/* -1 indicates the current user */
> > > +	if (_uid == (uid_t)-1) {
> > > +		uid = current_uid();
> >
> > Isn't it possible to have a valid uid of (unsigned int)-1? I know that
> > at least some sites use that for "nobody". Why not just require passing
> > in the correct UID?
> 
> See setresuid() and co. - there -1 is "don't change".
> 
> > Looks good overall, but I share Daniel's concerns about making
> > krb5-specific infrastructure like this. Essentially this is just a
> > persistent keyring that's associated with a kuid, right? Perhaps this
> > could be done in such a way that it could be usable for other
> > applications in the future?
> 
> It's not too hard, I suppose:
> 
> 	keyctl_get_persistent(uid, prefix, destring)
> 
> eg:
> 
> 	keyctl_get_persistent(-1, "_krb.", KEYCTL_SPEC_PROCESS_KEYRING)
> 
> giving:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring
> 		\___ _krb.0 keyring
> 		\___ _krb.5000 keyring
> 		\___ _krb.5001 keyring
> 		|	\___ tkt785 big_key
> 		|	\___ tkt12345 big_key
> 		\___ _afs.5000 keyring
> 			\___ afs.redhat.com rxrpc
> 
> The other way to do it is create one keyring per user and let userspace create
> subkeyrings under that:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring
> 		\___ _uid_p.0 keyring
> 		\___ _uid_p.5000 keyring
> 		\___ _uid_p.5001 keyring
> 			\___ krb keyring
> 			|	\___ tkt785 big_key
> 			|	\___ tkt12345 big_key
> 			\___ afs keyring
> 				\___ afs.redhat.com rxrpc
> 
> In the above scheme, it might be worth just making these the same as the user
> keyring - which means KEYCTL_SPEC_USER_KEYRING will automatically target it.
> 
> Simo:  I believe the problem you have with the user keyring is that it's not
> persistent beyond the life of the processes of that UID, right?

Correct.

Simo.
David Howells Aug. 2, 2013, 5 p.m. UTC | #10
Eric W. Biederman <ebiederm@xmission.com> wrote:

> > Add support for per-user_namespace registers of persistent per-UID kerberos
> > caches held within the kernel.
> 
> Out of curiosity is this cache per user namspace because the key lookup
> is per user namespace?

Yes.  You can't see keys in another namespace.  I occasionally wonder if I
should make the key serial number tree per namespace so that you don't search
keys outside your namespace and can't try looking them up by ID - but it
complicates the garbage collector which iterates over the entire tree (though
it could maintain a list of per-ns trees).

> Some minor nits below. But I don't see anything particulary scary about
> this patch.  Other than seeming to make it easy for root to get my
> kerbose tickets.

Root can do that anyway with file-based ccaches, I believe.  However, you can
change the key permissions to prevent root even seeing that your keys/keyrings
exist, let alone stealing them.

--
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. 2, 2013, 5:02 p.m. UTC | #11
Simo Sorce <simo@redhat.com> wrote:

> > Simo:  I believe the problem you have with the user keyring is that it's not
> > persistent beyond the life of the processes of that UID, right?
> 
> Correct.

In which case, pinning the user keyring in this fashion should make this work
without the need to add another keyctl, I think.

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
David Howells Aug. 2, 2013, 5:05 p.m. UTC | #12
Eric W. Biederman <ebiederm@xmission.com> wrote:

> > The cache returned is a keyring named "_krb.<uid>" that the possessor can
> > read, search, clear, invalidate, unlink from and add links to.  SELinux
> > and co. get a say as to whether this call will succeed as the caller must
> > have LINK permission on the cache keyring.
> 
> I think it would be more accurate to say you use the existing LSM
> security hooks for security keys.

Yes.

> Calling out SELinux in particular just seems odd as there is absolutely
> nothing SELinux specific in this patch.

Sorry, I normally think of SELinux as that's what I usually deal with.  Yes,
any and all LSMs.

> > +		    !nsown_capable(CAP_SETUID))
>
> You you make this ns_capable(ns, CAP_SETUID);
> 
> nsown_capable is the right thing here but I am trying to remove the
> function because it makes it too easy to not think about which
> user namespace you are in.

Okay.

> > +	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));
> 
> Please don't use the implementation detail __kuid_val.  Please use
> from_kuid(&init_user_ns, uid) instead so it is explicitly documented
> which user namespace you are using.

Actually, I don't want that either.  I want the user-visible UID from the
namespace.

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
David Howells Aug. 2, 2013, 5:12 p.m. UTC | #13
Daniel Kahn Gillmor <dkg@fifthhorseman.net> wrote:

> > The uid is -1 or the user's own UID for the user's own cache or the uid of
> > some other user's cache (requires CAP_SETUID).  This permits rpc.gssd or
> > whatever to mess with the cache.
> 
> Is the goal here eventually to be able to avoid the upcall to rpc.gssd
> entirely?  It seems a little bit roundabout to have the kernel call up
> into userspace for the credentials, only to talk to a process which then
> calls back into the kernel for something that the kernel has already
> well-defined internally.

I would like to see use of request_key() eventually so that the kernel can
fish a key directly out of the keyring if it needs one.  My kAFS filesystem
could pretty much use that immediately.

However, I don't really want to put all the Kerberos communications into the
kernel.  The keys obtained by request_key() on behalf of a filesystem might be
kerberos tickets or they might be something else.

> It seems like a non-privileged user could use this to store arbitrary data
> in this keyring as a way of hiding what would otherwise be filesystem
> activity or using it for some sort of odd/sneaky IPC mechanism.  Is this an
> intentional side effect?

It is true that a non-privileged user can store arbitrary keys in their
keyring and they could use it as an IPC mechanism if they really wanted to -
but if they could do that, they could use socketpairs, pipes or shm instead so
I'm not sure they can do anything that they couldn't do by some other
mechanism anyway.

Note that key accesses are regulated by the active LSM and I think can appear
in the audit log.

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
Jeff Layton Aug. 2, 2013, 5:13 p.m. UTC | #14
On Fri, 02 Aug 2013 17:53:25 +0100
David Howells <dhowells@redhat.com> wrote:

> Jeff Layton <jlayton@redhat.com> wrote:
> 
> > > +	/* -1 indicates the current user */
> > > +	if (_uid == (uid_t)-1) {
> > > +		uid = current_uid();
> >
> > Isn't it possible to have a valid uid of (unsigned int)-1? I know that
> > at least some sites use that for "nobody". Why not just require passing
> > in the correct UID?
> 
> See setresuid() and co. - there -1 is "don't change".
> 

<facepalm>

Good point. I got confused between -1 and -2. I think Solaris uses
(uid_t)-2 for nobody. Using -1 in this case should be fine.

> > Looks good overall, but I share Daniel's concerns about making
> > krb5-specific infrastructure like this. Essentially this is just a
> > persistent keyring that's associated with a kuid, right? Perhaps this
> > could be done in such a way that it could be usable for other
> > applications in the future?
> 
> It's not too hard, I suppose:
> 
> 	keyctl_get_persistent(uid, prefix, destring)
> 
> eg:
> 
> 	keyctl_get_persistent(-1, "_krb.", KEYCTL_SPEC_PROCESS_KEYRING)
> 
> giving:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring
> 		\___ _krb.0 keyring
> 		\___ _krb.5000 keyring
> 		\___ _krb.5001 keyring
> 		|	\___ tkt785 big_key
> 		|	\___ tkt12345 big_key
> 		\___ _afs.5000 keyring
> 			\___ afs.redhat.com rxrpc
> 
> The other way to do it is create one keyring per user and let userspace create
> subkeyrings under that:
> 
> 	struct user_namespace
> 	  \___ .krb_cache keyring
> 		\___ _uid_p.0 keyring
> 		\___ _uid_p.5000 keyring
> 		\___ _uid_p.5001 keyring
> 			\___ krb keyring
> 			|	\___ tkt785 big_key
> 			|	\___ tkt12345 big_key
> 			\___ afs keyring
> 				\___ afs.redhat.com rxrpc
> 


That's probably what I'd suggest. Allow one persistent keyring per
user, and expect userland to organize things sanely under it.

nit: I probably wouldn't call the top-level keyring "krb_cache"
though ;)

> In the above scheme, it might be worth just making these the same as the user
> keyring - which means KEYCTL_SPEC_USER_KEYRING will automatically target it.
> 
> Simo:  I believe the problem you have with the user keyring is that it's not
> persistent beyond the life of the processes of that UID, right?
> 

Possibly. It really comes down to what sort of lifecycle you expect here.

Some applications might be caught by surprise if the per-user keyring
was already populated in certain situations. OTOH, they have the same
problem if there's even one running process with that uid so maybe it's
not a big deal.

If you do this, it might make sense to allow the admin to tune the
expiry sysctl in such a way that user keyrings go away as soon as
the last reference is gone (maybe by setting it to 0?).
Eric W. Biederman Aug. 2, 2013, 5:44 p.m. UTC | #15
David Howells <dhowells@redhat.com> writes:

> Eric W. Biederman <ebiederm@xmission.com> wrote:
>
>> > The cache returned is a keyring named "_krb.<uid>" that the possessor can
>> > read, search, clear, invalidate, unlink from and add links to.  SELinux
>> > and co. get a say as to whether this call will succeed as the caller must
>> > have LINK permission on the cache keyring.
>> 
>> I think it would be more accurate to say you use the existing LSM
>> security hooks for security keys.
>
> Yes.
>
>> Calling out SELinux in particular just seems odd as there is absolutely
>> nothing SELinux specific in this patch.
>
> Sorry, I normally think of SELinux as that's what I usually deal with.  Yes,
> any and all LSMs.
>
>> > +		    !nsown_capable(CAP_SETUID))
>>
>> You you make this ns_capable(ns, CAP_SETUID);
>> 
>> nsown_capable is the right thing here but I am trying to remove the
>> function because it makes it too easy to not think about which
>> user namespace you are in.
>
> Okay.
>
>> > +	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));
>> 
>> Please don't use the implementation detail __kuid_val.  Please use
>> from_kuid(&init_user_ns, uid) instead so it is explicitly documented
>> which user namespace you are using.
>
> Actually, I don't want that either.  I want the user-visible UID from the
> namespace.

Which is a definite reason to use from_kuid().  So you think about which
user namespace you want this to be seen in.

I guess if this is all in the user namespace from_kuid(ns, uid) is what
you are after.  I was thinking this was heading to the upcall which only
runs in the initial user namespace.  When passing things to the upcall
it makes sense to use values in the initial user namespace.

Eric
--
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. 2, 2013, 5:50 p.m. UTC | #16
Daniel Kahn Gillmor <dkg@fifthhorseman.net> wrote:

> I guess this raises the question from a different perspective: if the
> kernel already supports arbitrary shm segments, filesystem locations,
> etc, which can be used for storing/passing opaque bytestrings between
> different parts of userspace, what advantages do we gain from having
> this new specific mechanism in the kernel?  Why couldn't those parts of
> userspace just rely on already-existing mechanisms instead of
> introducing this new interface?

Kernel services can use keys directly by calling request_key() without
necessarily having to upcall.


The keys service in the kernel was created initially for use by the kAFS
filesystem in the kernel which uses it for passing authentication tokens
around.  Let me describe how I originally envisioned this working as it may
help the discussion.


Every time a non-fd-based operation[*] is called, kAFS calls request_key() to
get a key containing an authentication token for the cell.

 [*] That is things like lookup, open(), rmdir() and stat() but not read() and
     write() which use a key cached in the file struct.

Keys can be preemptively added for kAFS by calling a klog or an aklog program
talk to the authentication server and then use add_key() to add a key into the
kernel.  The filesystem subsequently finds this and uses it without the need to
upcall.

Currently, that's all you can do with kAFS.

However, on my todo list is to make it able to upcall when it doesn't have a
key available.  This is provided for by the request_key() mechanism.
request_key() searches for the key and if one if is available, it returns it
otherwise it can[*] partially construct a key and then upcall to ask userspace
to instantiate it.

 [*] If the caller permits it to.

request_key() can upcall to a program it execs itself or it can upcall to a
running program (though the exact mechanism of the latter is to be worked out).

Say userspace then decides to do a Kerberos ticket request.  It could call
request_key() itself to search the original process's keyrings for a TGT which
it can then use to request a ticket.

Once it has the ticket, it can place the ticket data into the partially
constructed key and link the key into the kerberos ccache keyring to retain it.


Now, one of the problems I've got is that the name in the partially constructed
key is from the filesystem's view of the world - and this doesn't necessarily
coincide with that of the authentication server.  For example, the kAFS
filesystem wants a key named for the cell, eg.:

	afs.redhat.com

but the Kerberos ticket might look like:

	AFS/afs.redhat.com@REDHAT.COM

Now, I do permit partial matching, so I could ignore the "@REDHAT.COM" and just
slap "AFS/" on the front of what I'm looking for.

A further problem is the fact that keys have types and search only finds the
type asked for - though that can be dealt with in the keyring search algorithm.

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. 2, 2013, 8:20 p.m. UTC | #17
On Fri, Aug 2, 2013 at 8:55 AM, Jeff Layton <jlayton@redhat.com> wrote:
> Isn't it possible to have a valid uid of (unsigned int)-1? I know that
> at least some sites use that for "nobody". Why not just require passing
> in the correct UID?

POSIX requires valid UIDs to be non-nengative.  POSIX does not require
uid_t to be signed or unsigned.  POSIX does make use of (uid_t)-1 as a
sentinel (e.g., in setreuid(2)).  (uid_t)-1 is special.  Do not use
it.

As an aside, note that on Solaris 10 and less uid_t was signed, so
UIDs 2^31..2^32-2 were unusable.  Interop with S10 and less requires
that you not use such UIDs.  Ditto gid_t and GIDs.

Also note that in Solaris 11 uid_t is unsigned BUT the range of UIDs
2^31..2^32-2 is still reserved (for automatic, on-demand,
non-persistent allocation for ID mapping purposes).  Interop with S11
requires that you not use such UIDs.  Ditto gid_t and GIDs.

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/linux/user_namespace.h b/include/linux/user_namespace.h
index b6b215f..3cce8c7 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -28,6 +28,12 @@  struct user_namespace {
 	unsigned int		proc_inum;
 	bool			may_mount_sysfs;
 	bool			may_mount_proc;
+
+	/* Register of per-UID Kerberos caches for this namespace */
+#ifdef CONFIG_KEYS_KERBEROS_CACHE
+	struct key		*krb_cache_register;
+	struct rw_semaphore	krb_cache_register_sem;
+#endif
 };
 
 extern struct user_namespace init_user_ns;
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index c9b7f4fa..a37c62b 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -56,5 +56,6 @@ 
 #define KEYCTL_REJECT			19	/* reject a partially constructed key */
 #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
 #define KEYCTL_INVALIDATE		21	/* invalidate a key */
+#define KEYCTL_GET_KRBCACHE		22	/* get a user's kerberos cache keyring */
 
 #endif /*  _LINUX_KEYCTL_H */
diff --git a/kernel/user.c b/kernel/user.c
index 69b4c3d..6c9e1b9 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -53,6 +53,10 @@  struct user_namespace init_user_ns = {
 	.proc_inum = PROC_USER_INIT_INO,
 	.may_mount_sysfs = true,
 	.may_mount_proc = true,
+#ifdef CONFIG_KEYS_KERBEROS_CACHE
+	.krb_cache_register_sem =
+	__RWSEM_INITIALIZER(init_user_ns.krb_cache_register_sem),
+#endif
 };
 EXPORT_SYMBOL_GPL(init_user_ns);
 
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index d8c30db..098d954 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -99,6 +99,7 @@  int create_user_ns(struct cred *new)
 
 	update_mnt_policy(ns);
 
+	rwsem_init(&ns->krb_cache_register_sem);
 	return 0;
 }
 
@@ -123,6 +124,7 @@  void free_user_ns(struct user_namespace *ns)
 
 	do {
 		parent = ns->parent;
+		key_put(ns->krb_cache_register);
 		proc_free_inum(ns->proc_inum);
 		kmem_cache_free(user_ns_cachep, ns);
 		ns = parent;
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index eafb335..ee3f5a5 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -20,6 +20,18 @@  config KEYS
 
 	  If you are unsure as to whether this is required, answer N.
 
+config KEYS_KERBEROS_CACHE
+	bool "Enable persistent keyring-based kerberos cache"
+	depends on KEYS
+	help
+	  This option provides a register of per-UID kerberos cache keyrings.
+	  A particular keyring may be accessed by either the user whose keyring
+	  it is or by a process with administrative privileges.  SELinux gets
+	  to rule on which admin-level processes get to access the cache.
+
+	  Keyrings are created and added into the register upon demand and get
+	  removed if they expire (a default timeout is set upon creation).
+
 config BIG_KEYS
 	tristate "Large payload keys"
 	depends on KEYS
diff --git a/security/keys/Makefile b/security/keys/Makefile
index c487c77..c168ad6 100644
--- a/security/keys/Makefile
+++ b/security/keys/Makefile
@@ -18,6 +18,7 @@  obj-y := \
 obj-$(CONFIG_KEYS_COMPAT) += compat.o
 obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_SYSCTL) += sysctl.o
+obj-$(CONFIG_KEYS_KERBEROS_CACHE) += krbcache.o
 
 #
 # Key types
diff --git a/security/keys/compat.c b/security/keys/compat.c
index d65fa7f..ead383b 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -138,6 +138,9 @@  asmlinkage long compat_sys_keyctl(u32 option,
 	case KEYCTL_INVALIDATE:
 		return keyctl_invalidate_key(arg2);
 
+	case KEYCTL_GET_KRBCACHE:
+		return keyctl_get_krbcache(arg2, arg3);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 581c6f6..fa379c6 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -255,6 +255,15 @@  extern long keyctl_invalidate_key(key_serial_t);
 extern long keyctl_instantiate_key_common(key_serial_t,
 					  const struct iovec *,
 					  unsigned, size_t, key_serial_t);
+#ifdef CONFIG_KEYS_KERBEROS_CACHE
+extern long keyctl_get_krbcache(uid_t, key_serial_t);
+extern unsigned krb_cache_expiry;
+#else
+static inline long keyctl_get_krbcache(uid_t uid, key_serial_t destring)
+{
+	return -EOPNOTSUPP;
+}
+#endif
 
 /*
  * Debugging key validation
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 33cfd27..c4fae05 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -1667,6 +1667,9 @@  SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case KEYCTL_INVALIDATE:
 		return keyctl_invalidate_key((key_serial_t) arg2);
 
+	case KEYCTL_GET_KRBCACHE:
+		return keyctl_get_krbcache((uid_t)arg2, (key_serial_t)arg3);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/krbcache.c b/security/keys/krbcache.c
new file mode 100644
index 0000000..4e2aa9c
--- /dev/null
+++ b/security/keys/krbcache.c
@@ -0,0 +1,132 @@ 
+/* Kerberos persistent cache register
+ *
+ * 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/user_namespace.h>
+#include "internal.h"
+
+unsigned krb_cache_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
+
+/*
+ * Get the Kerberos cache keyring for a specific UID and link it to the
+ * nominated keyring.
+ */
+long keyctl_get_krbcache(uid_t _uid, key_serial_t destid)
+{
+	struct user_namespace *ns = current_user_ns();
+	struct keyring_index_key index_key;
+	struct key *krb;
+	key_ref_t reg_ref, dest_ref, krb_ref;
+	kuid_t uid;
+	char buf[24];
+	long ret;
+
+	/* -1 indicates the current user */
+	if (_uid == (uid_t)-1) {
+		uid = current_uid();
+	} else {
+		uid = make_kuid(ns, _uid);
+		if (!uid_valid(uid))
+			return -EINVAL;
+
+		/* You can only see your own kerberos cache if you're not
+		 * sufficiently privileged.
+		 */
+		if (uid != current_uid() &&
+		    uid != current_suid() &&
+		    uid != current_euid() &&
+		    uid != current_fsuid() &&
+		    !nsown_capable(CAP_SETUID))
+			return -EPERM;
+	}
+
+	/* There must be a destination keyring */
+	dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE);
+	if (IS_ERR(dest_ref))
+		return PTR_ERR(dest_ref);
+	if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
+		ret = -ENOTDIR;
+		goto out_put_dest;
+	}
+
+	/* Look in the register if it exists */
+	index_key.type = &key_type_keyring;
+	index_key.description = buf;
+	index_key.desc_len = sprintf(buf, "_krb.%u", __kuid_val(uid));
+
+	if (ns->krb_cache_register) {
+		reg_ref = make_key_ref(ns->krb_cache_register, true);
+		down_read(&ns->krb_cache_register_sem);
+		krb_ref = find_key_to_update(reg_ref, &index_key);
+		up_read(&ns->krb_cache_register_sem);
+
+		if (krb_ref)
+			goto found;
+	}
+
+	/* It wasn't in the register, so we'll need to create it.  We might
+	 * also need to create the register.
+	 */
+	down_write(&ns->krb_cache_register_sem);
+
+	if (!ns->krb_cache_register) {
+		struct key *reg = 
+			keyring_alloc(".krb_cache",
+				      KUIDT_INIT(0), KGIDT_INIT(0),
+				      current_cred(),
+				      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
+				       KEY_USR_VIEW | KEY_USR_READ),
+				      KEY_ALLOC_NOT_IN_QUOTA, NULL);
+		if (IS_ERR(reg)) {
+			up_write(&ns->krb_cache_register_sem);
+			ret = PTR_ERR(reg);
+			goto out_put_dest;
+		}
+
+		ns->krb_cache_register = reg;
+	} else {
+		reg_ref = make_key_ref(ns->krb_cache_register, true);
+		krb_ref = find_key_to_update(reg_ref, &index_key);
+		if (krb_ref) {
+			up_write(&ns->krb_cache_register_sem);
+			goto found;
+		}
+	}
+
+	krb = keyring_alloc(buf,
+			    uid, INVALID_GID, current_cred(),
+			    ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
+			     KEY_USR_VIEW | KEY_USR_READ),
+			    KEY_ALLOC_NOT_IN_QUOTA,
+			    ns->krb_cache_register);
+	up_write(&ns->krb_cache_register_sem);
+	if (!IS_ERR(krb)) {
+		krb_ref = make_key_ref(krb, true);
+		goto found;
+	}
+
+out_put_krb:
+	key_ref_to_ptr(krb_ref);
+out_put_dest:
+	key_ref_to_ptr(dest_ref);
+	return ret;
+
+found:
+	ret = key_task_permission(krb_ref, current_cred(), KEY_LINK);
+	if (ret < 0)
+		goto out_put_krb;
+
+	ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(krb_ref));
+	if (ret == 0) {
+		key_set_timeout(key_ref_to_ptr(krb_ref), krb_cache_expiry);
+		ret = key_ref_to_ptr(krb_ref)->serial;		
+	}
+	goto out_put_krb;
+}
diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c
index ee32d18..3af1798 100644
--- a/security/keys/sysctl.c
+++ b/security/keys/sysctl.c
@@ -61,5 +61,16 @@  ctl_table key_sysctls[] = {
 		.extra1 = (void *) &zero,
 		.extra2 = (void *) &max,
 	},
+#ifdef CONFIG_KEYS_KERBEROS_CACHE
+	{
+		.procname = "krb_expiry",
+		.data = &krb_cache_expiry,
+		.maxlen = sizeof(unsigned),
+		.mode = 0644,
+		.proc_handler = proc_dointvec_minmax,
+		.extra1 = (void *) &zero,
+		.extra2 = (void *) &max,
+	},
+#endif
 	{ }
 };