mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-01 00:58:39 +02:00 
			
		
		
		
	KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches
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. Active LSMs get a chance to rule on whether the caller is permitted to make a link. 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> Tested-by: Simo Sorce <simo@redhat.com> cc: Serge E. Hallyn <serge.hallyn@ubuntu.com> cc: Eric W. Biederman <ebiederm@xmission.com>
This commit is contained in:
		
							parent
							
								
									ab3c3587f8
								
							
						
					
					
						commit
						f36f8c75ae
					
				
					 11 changed files with 230 additions and 0 deletions
				
			
		|  | @ -27,6 +27,12 @@ struct user_namespace { | ||||||
| 	kuid_t			owner; | 	kuid_t			owner; | ||||||
| 	kgid_t			group; | 	kgid_t			group; | ||||||
| 	unsigned int		proc_inum; | 	unsigned int		proc_inum; | ||||||
|  | 
 | ||||||
|  | 	/* Register of per-UID persistent keyrings for this namespace */ | ||||||
|  | #ifdef CONFIG_PERSISTENT_KEYRINGS | ||||||
|  | 	struct key		*persistent_keyring_register; | ||||||
|  | 	struct rw_semaphore	persistent_keyring_register_sem; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern struct user_namespace init_user_ns; | extern struct user_namespace init_user_ns; | ||||||
|  |  | ||||||
|  | @ -56,5 +56,6 @@ | ||||||
| #define KEYCTL_REJECT			19	/* reject a partially constructed key */ | #define KEYCTL_REJECT			19	/* reject a partially constructed key */ | ||||||
| #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */ | #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */ | ||||||
| #define KEYCTL_INVALIDATE		21	/* invalidate a key */ | #define KEYCTL_INVALIDATE		21	/* invalidate a key */ | ||||||
|  | #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */ | ||||||
| 
 | 
 | ||||||
| #endif /*  _LINUX_KEYCTL_H */ | #endif /*  _LINUX_KEYCTL_H */ | ||||||
|  |  | ||||||
|  | @ -51,6 +51,10 @@ struct user_namespace init_user_ns = { | ||||||
| 	.owner = GLOBAL_ROOT_UID, | 	.owner = GLOBAL_ROOT_UID, | ||||||
| 	.group = GLOBAL_ROOT_GID, | 	.group = GLOBAL_ROOT_GID, | ||||||
| 	.proc_inum = PROC_USER_INIT_INO, | 	.proc_inum = PROC_USER_INIT_INO, | ||||||
|  | #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); | EXPORT_SYMBOL_GPL(init_user_ns); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -101,6 +101,9 @@ int create_user_ns(struct cred *new) | ||||||
| 
 | 
 | ||||||
| 	set_cred_user_ns(new, ns); | 	set_cred_user_ns(new, ns); | ||||||
| 
 | 
 | ||||||
|  | #ifdef CONFIG_PERSISTENT_KEYRINGS | ||||||
|  | 	init_rwsem(&ns->persistent_keyring_register_sem); | ||||||
|  | #endif | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -130,6 +133,9 @@ void free_user_ns(struct user_namespace *ns) | ||||||
| 
 | 
 | ||||||
| 	do { | 	do { | ||||||
| 		parent = ns->parent; | 		parent = ns->parent; | ||||||
|  | #ifdef CONFIG_PERSISTENT_KEYRINGS | ||||||
|  | 		key_put(ns->persistent_keyring_register); | ||||||
|  | #endif | ||||||
| 		proc_free_inum(ns->proc_inum); | 		proc_free_inum(ns->proc_inum); | ||||||
| 		kmem_cache_free(user_ns_cachep, ns); | 		kmem_cache_free(user_ns_cachep, ns); | ||||||
| 		ns = parent; | 		ns = parent; | ||||||
|  |  | ||||||
|  | @ -20,6 +20,23 @@ config KEYS | ||||||
| 
 | 
 | ||||||
| 	  If you are unsure as to whether this is required, answer N. | 	  If you are unsure as to whether this is required, answer N. | ||||||
| 
 | 
 | ||||||
|  | config PERSISTENT_KEYRINGS | ||||||
|  | 	bool "Enable register of persistent per-UID keyrings" | ||||||
|  | 	depends on KEYS | ||||||
|  | 	help | ||||||
|  | 	  This option provides a register of persistent per-UID keyrings, | ||||||
|  | 	  primarily aimed at Kerberos key storage.  The keyrings are persistent | ||||||
|  | 	  in the sense that they stay around after all processes of that UID | ||||||
|  | 	  have exited, not that they survive the machine being rebooted. | ||||||
|  | 
 | ||||||
|  | 	  A particular keyring may be accessed by either the user whose keyring | ||||||
|  | 	  it is or by a process with administrative privileges.  The active | ||||||
|  | 	  LSMs 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 | config BIG_KEYS | ||||||
| 	tristate "Large payload keys" | 	tristate "Large payload keys" | ||||||
| 	depends on KEYS | 	depends on KEYS | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ obj-y := \ | ||||||
| obj-$(CONFIG_KEYS_COMPAT) += compat.o | obj-$(CONFIG_KEYS_COMPAT) += compat.o | ||||||
| obj-$(CONFIG_PROC_FS) += proc.o | obj-$(CONFIG_PROC_FS) += proc.o | ||||||
| obj-$(CONFIG_SYSCTL) += sysctl.o | obj-$(CONFIG_SYSCTL) += sysctl.o | ||||||
|  | obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o | ||||||
| 
 | 
 | ||||||
| #
 | #
 | ||||||
| # Key types
 | # Key types
 | ||||||
|  |  | ||||||
|  | @ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option, | ||||||
| 	case KEYCTL_INVALIDATE: | 	case KEYCTL_INVALIDATE: | ||||||
| 		return keyctl_invalidate_key(arg2); | 		return keyctl_invalidate_key(arg2); | ||||||
| 
 | 
 | ||||||
|  | 	case KEYCTL_GET_PERSISTENT: | ||||||
|  | 		return keyctl_get_persistent(arg2, arg3); | ||||||
|  | 
 | ||||||
| 	default: | 	default: | ||||||
| 		return -EOPNOTSUPP; | 		return -EOPNOTSUPP; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t); | ||||||
| extern long keyctl_instantiate_key_common(key_serial_t, | extern long keyctl_instantiate_key_common(key_serial_t, | ||||||
| 					  const struct iovec *, | 					  const struct iovec *, | ||||||
| 					  unsigned, size_t, key_serial_t); | 					  unsigned, size_t, key_serial_t); | ||||||
|  | #ifdef CONFIG_PERSISTENT_KEYRINGS | ||||||
|  | extern long keyctl_get_persistent(uid_t, key_serial_t); | ||||||
|  | extern unsigned persistent_keyring_expiry; | ||||||
|  | #else | ||||||
|  | static inline long keyctl_get_persistent(uid_t uid, key_serial_t destring) | ||||||
|  | { | ||||||
|  | 	return -EOPNOTSUPP; | ||||||
|  | } | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Debugging key validation |  * Debugging key validation | ||||||
|  |  | ||||||
|  | @ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, | ||||||
| 	case KEYCTL_INVALIDATE: | 	case KEYCTL_INVALIDATE: | ||||||
| 		return keyctl_invalidate_key((key_serial_t) arg2); | 		return keyctl_invalidate_key((key_serial_t) arg2); | ||||||
| 
 | 
 | ||||||
|  | 	case KEYCTL_GET_PERSISTENT: | ||||||
|  | 		return keyctl_get_persistent((uid_t)arg2, (key_serial_t)arg3); | ||||||
|  | 
 | ||||||
| 	default: | 	default: | ||||||
| 		return -EOPNOTSUPP; | 		return -EOPNOTSUPP; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
							
								
								
									
										169
									
								
								security/keys/persistent.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								security/keys/persistent.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,169 @@ | ||||||
|  | /* General persistent per-UID keyrings 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 persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */ | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Create the persistent keyring register for the current user namespace. | ||||||
|  |  * | ||||||
|  |  * Called with the namespace's sem locked for writing. | ||||||
|  |  */ | ||||||
|  | static int key_create_persistent_register(struct user_namespace *ns) | ||||||
|  | { | ||||||
|  | 	struct key *reg = keyring_alloc(".persistent_register", | ||||||
|  | 					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)) | ||||||
|  | 		return PTR_ERR(reg); | ||||||
|  | 
 | ||||||
|  | 	ns->persistent_keyring_register = reg; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Create the persistent keyring for the specified user. | ||||||
|  |  * | ||||||
|  |  * Called with the namespace's sem locked for writing. | ||||||
|  |  */ | ||||||
|  | static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, | ||||||
|  | 				       struct keyring_index_key *index_key) | ||||||
|  | { | ||||||
|  | 	struct key *persistent; | ||||||
|  | 	key_ref_t reg_ref, persistent_ref; | ||||||
|  | 
 | ||||||
|  | 	if (!ns->persistent_keyring_register) { | ||||||
|  | 		long err = key_create_persistent_register(ns); | ||||||
|  | 		if (err < 0) | ||||||
|  | 			return ERR_PTR(err); | ||||||
|  | 	} else { | ||||||
|  | 		reg_ref = make_key_ref(ns->persistent_keyring_register, true); | ||||||
|  | 		persistent_ref = find_key_to_update(reg_ref, index_key); | ||||||
|  | 		if (persistent_ref) | ||||||
|  | 			return persistent_ref; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	persistent = keyring_alloc(index_key->description, | ||||||
|  | 				   uid, INVALID_GID, current_cred(), | ||||||
|  | 				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) | | ||||||
|  | 				    KEY_USR_VIEW | KEY_USR_READ), | ||||||
|  | 				   KEY_ALLOC_NOT_IN_QUOTA, | ||||||
|  | 				   ns->persistent_keyring_register); | ||||||
|  | 	if (IS_ERR(persistent)) | ||||||
|  | 		return ERR_CAST(persistent); | ||||||
|  | 
 | ||||||
|  | 	return make_key_ref(persistent, true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Get the persistent keyring for a specific UID and link it to the nominated | ||||||
|  |  * keyring. | ||||||
|  |  */ | ||||||
|  | static long key_get_persistent(struct user_namespace *ns, kuid_t uid, | ||||||
|  | 			       key_ref_t dest_ref) | ||||||
|  | { | ||||||
|  | 	struct keyring_index_key index_key; | ||||||
|  | 	struct key *persistent; | ||||||
|  | 	key_ref_t reg_ref, persistent_ref; | ||||||
|  | 	char buf[32]; | ||||||
|  | 	long ret; | ||||||
|  | 
 | ||||||
|  | 	/* Look in the register if it exists */ | ||||||
|  | 	index_key.type = &key_type_keyring; | ||||||
|  | 	index_key.description = buf; | ||||||
|  | 	index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid)); | ||||||
|  | 
 | ||||||
|  | 	if (ns->persistent_keyring_register) { | ||||||
|  | 		reg_ref = make_key_ref(ns->persistent_keyring_register, true); | ||||||
|  | 		down_read(&ns->persistent_keyring_register_sem); | ||||||
|  | 		persistent_ref = find_key_to_update(reg_ref, &index_key); | ||||||
|  | 		up_read(&ns->persistent_keyring_register_sem); | ||||||
|  | 
 | ||||||
|  | 		if (persistent_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->persistent_keyring_register_sem); | ||||||
|  | 	persistent_ref = key_create_persistent(ns, uid, &index_key); | ||||||
|  | 	up_write(&ns->persistent_keyring_register_sem); | ||||||
|  | 	if (!IS_ERR(persistent_ref)) | ||||||
|  | 		goto found; | ||||||
|  | 
 | ||||||
|  | 	return PTR_ERR(persistent_ref); | ||||||
|  | 
 | ||||||
|  | found: | ||||||
|  | 	ret = key_task_permission(persistent_ref, current_cred(), KEY_LINK); | ||||||
|  | 	if (ret == 0) { | ||||||
|  | 		persistent = key_ref_to_ptr(persistent_ref); | ||||||
|  | 		ret = key_link(key_ref_to_ptr(dest_ref), persistent); | ||||||
|  | 		if (ret == 0) { | ||||||
|  | 			key_set_timeout(persistent, persistent_keyring_expiry); | ||||||
|  | 			ret = persistent->serial;		 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	key_ref_put(persistent_ref); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Get the persistent keyring for a specific UID and link it to the nominated | ||||||
|  |  * keyring. | ||||||
|  |  */ | ||||||
|  | long keyctl_get_persistent(uid_t _uid, key_serial_t destid) | ||||||
|  | { | ||||||
|  | 	struct user_namespace *ns = current_user_ns(); | ||||||
|  | 	key_ref_t dest_ref; | ||||||
|  | 	kuid_t uid; | ||||||
|  | 	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 persistent cache if you're not
 | ||||||
|  | 		 * sufficiently privileged. | ||||||
|  | 		 */ | ||||||
|  | 		if (uid_eq(uid, current_uid()) && | ||||||
|  | 		    uid_eq(uid, current_suid()) && | ||||||
|  | 		    uid_eq(uid, current_euid()) && | ||||||
|  | 		    uid_eq(uid, current_fsuid()) && | ||||||
|  | 		    !ns_capable(ns, 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; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ret = key_get_persistent(ns, uid, dest_ref); | ||||||
|  | 
 | ||||||
|  | out_put_dest: | ||||||
|  | 	key_ref_put(dest_ref); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | @ -61,5 +61,16 @@ ctl_table key_sysctls[] = { | ||||||
| 		.extra1 = (void *) &zero, | 		.extra1 = (void *) &zero, | ||||||
| 		.extra2 = (void *) &max, | 		.extra2 = (void *) &max, | ||||||
| 	}, | 	}, | ||||||
|  | #ifdef CONFIG_PERSISTENT_KEYRINGS | ||||||
|  | 	{ | ||||||
|  | 		.procname = "persistent_keyring_expiry", | ||||||
|  | 		.data = &persistent_keyring_expiry, | ||||||
|  | 		.maxlen = sizeof(unsigned), | ||||||
|  | 		.mode = 0644, | ||||||
|  | 		.proc_handler = proc_dointvec_minmax, | ||||||
|  | 		.extra1 = (void *) &zero, | ||||||
|  | 		.extra2 = (void *) &max, | ||||||
|  | 	}, | ||||||
|  | #endif | ||||||
| 	{ } | 	{ } | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 David Howells
						David Howells