Commit 9a56c2db authored by Eric W. Biederman's avatar Eric W. Biederman
Browse files

userns: Convert security/keys to the new userns infrastructure



- Replace key_user ->user_ns equality checks with kuid_has_mapping checks.
- Use from_kuid to generate key descriptions
- Use kuid_t and kgid_t and the associated helpers instead of uid_t and gid_t
- Avoid potential problems with file descriptor passing by displaying
  keys in the user namespace of the opener of key status proc files.

Cc: linux-security-module@vger.kernel.org
Cc: keyrings@linux-nfs.org
Cc: David Howells <dhowells@redhat.com>
Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
parent 5fce5e0b
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/atomic.h>

#ifdef __KERNEL__
#include <linux/uidgid.h>

/* key handle serial number */
typedef int32_t key_serial_t;
@@ -137,8 +138,8 @@ struct key {
		time_t		revoked_at;	/* time at which key was revoked */
	};
	time_t			last_used_at;	/* last time used for LRU keyring discard */
	uid_t			uid;
	gid_t			gid;
	kuid_t			uid;
	kgid_t			gid;
	key_perm_t		perm;		/* access permissions */
	unsigned short		quotalen;	/* length added to quota */
	unsigned short		datalen;	/* payload data length
@@ -193,7 +194,7 @@ struct key {

extern struct key *key_alloc(struct key_type *type,
			     const char *desc,
			     uid_t uid, gid_t gid,
			     kuid_t uid, kgid_t gid,
			     const struct cred *cred,
			     key_perm_t perm,
			     unsigned long flags);
@@ -262,7 +263,7 @@ extern int key_link(struct key *keyring,
extern int key_unlink(struct key *keyring,
		      struct key *key);

extern struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid,
extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
				 const struct cred *cred,
				 unsigned long flags,
				 struct key *dest);
+0 −1
Original line number Diff line number Diff line
@@ -927,7 +927,6 @@ config UIDGID_CONVERTED
	# Features
	depends on IMA = n
	depends on EVM = n
	depends on KEYS = n
	depends on AUDIT = n
	depends on AUDITSYSCALL = n
	depends on TASKSTATS = n
+2 −4
Original line number Diff line number Diff line
@@ -52,8 +52,7 @@ struct key_user {
	atomic_t		usage;		/* for accessing qnkeys & qnbytes */
	atomic_t		nkeys;		/* number of keys */
	atomic_t		nikeys;		/* number of instantiated keys */
	uid_t			uid;
	struct user_namespace	*user_ns;
	kuid_t			uid;
	int			qnkeys;		/* number of keys allocated to this user */
	int			qnbytes;	/* number of bytes allocated to this user */
};
@@ -62,8 +61,7 @@ extern struct rb_root key_user_tree;
extern spinlock_t	key_user_lock;
extern struct key_user	root_key_user;

extern struct key_user *key_user_lookup(uid_t uid,
					struct user_namespace *user_ns);
extern struct key_user *key_user_lookup(kuid_t uid);
extern void key_user_put(struct key_user *user);

/*
+8 −15
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@
#include <linux/workqueue.h>
#include <linux/random.h>
#include <linux/err.h>
#include <linux/user_namespace.h>
#include "internal.h"

struct kmem_cache *key_jar;
@@ -52,7 +51,7 @@ void __key_check(const struct key *key)
 * Get the key quota record for a user, allocating a new record if one doesn't
 * already exist.
 */
struct key_user *key_user_lookup(uid_t uid, struct user_namespace *user_ns)
struct key_user *key_user_lookup(kuid_t uid)
{
	struct key_user *candidate = NULL, *user;
	struct rb_node *parent = NULL;
@@ -67,13 +66,9 @@ try_again:
		parent = *p;
		user = rb_entry(parent, struct key_user, node);

		if (uid < user->uid)
		if (uid_lt(uid, user->uid))
			p = &(*p)->rb_left;
		else if (uid > user->uid)
			p = &(*p)->rb_right;
		else if (user_ns < user->user_ns)
			p = &(*p)->rb_left;
		else if (user_ns > user->user_ns)
		else if (uid_gt(uid, user->uid))
			p = &(*p)->rb_right;
		else
			goto found;
@@ -102,7 +97,6 @@ try_again:
	atomic_set(&candidate->nkeys, 0);
	atomic_set(&candidate->nikeys, 0);
	candidate->uid = uid;
	candidate->user_ns = get_user_ns(user_ns);
	candidate->qnkeys = 0;
	candidate->qnbytes = 0;
	spin_lock_init(&candidate->lock);
@@ -131,7 +125,6 @@ void key_user_put(struct key_user *user)
	if (atomic_dec_and_lock(&user->usage, &key_user_lock)) {
		rb_erase(&user->node, &key_user_tree);
		spin_unlock(&key_user_lock);
		put_user_ns(user->user_ns);

		kfree(user);
	}
@@ -229,7 +222,7 @@ serial_exists:
 * key_alloc() calls don't race with module unloading.
 */
struct key *key_alloc(struct key_type *type, const char *desc,
		      uid_t uid, gid_t gid, const struct cred *cred,
		      kuid_t uid, kgid_t gid, const struct cred *cred,
		      key_perm_t perm, unsigned long flags)
{
	struct key_user *user = NULL;
@@ -253,16 +246,16 @@ struct key *key_alloc(struct key_type *type, const char *desc,
	quotalen = desclen + type->def_datalen;

	/* get hold of the key tracking for this user */
	user = key_user_lookup(uid, cred->user_ns);
	user = key_user_lookup(uid);
	if (!user)
		goto no_memory_1;

	/* check that the user's quota permits allocation of another key and
	 * its description */
	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) {
		unsigned maxkeys = (uid == 0) ?
		unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ?
			key_quota_root_maxkeys : key_quota_maxkeys;
		unsigned maxbytes = (uid == 0) ?
		unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ?
			key_quota_root_maxbytes : key_quota_maxbytes;

		spin_lock(&user->lock);
@@ -380,7 +373,7 @@ int key_payload_reserve(struct key *key, size_t datalen)

	/* contemplate the quota adjustment */
	if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
		unsigned maxbytes = (key->user->uid == 0) ?
		unsigned maxbytes = uid_eq(key->user->uid, GLOBAL_ROOT_UID) ?
			key_quota_root_maxbytes : key_quota_maxbytes;

		spin_lock(&key->user->lock);
+30 −20
Original line number Diff line number Diff line
@@ -569,8 +569,8 @@ okay:
	ret = snprintf(tmpbuf, PAGE_SIZE - 1,
		       "%s;%d;%d;%08x;%s",
		       key->type->name,
		       key->uid,
		       key->gid,
		       from_kuid_munged(current_user_ns(), key->uid),
		       from_kgid_munged(current_user_ns(), key->gid),
		       key->perm,
		       key->description ?: "");

@@ -766,15 +766,25 @@ error:
 *
 * If successful, 0 will be returned.
 */
long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
{
	struct key_user *newowner, *zapowner = NULL;
	struct key *key;
	key_ref_t key_ref;
	long ret;
	kuid_t uid;
	kgid_t gid;

	uid = make_kuid(current_user_ns(), user);
	gid = make_kgid(current_user_ns(), group);
	ret = -EINVAL;
	if ((user != (uid_t) -1) && !uid_valid(uid))
		goto error;
	if ((group != (gid_t) -1) && !gid_valid(gid))
		goto error;

	ret = 0;
	if (uid == (uid_t) -1 && gid == (gid_t) -1)
	if (user == (uid_t) -1 && group == (gid_t) -1)
		goto error;

	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
@@ -792,27 +802,27 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)

	if (!capable(CAP_SYS_ADMIN)) {
		/* only the sysadmin can chown a key to some other UID */
		if (uid != (uid_t) -1 && key->uid != uid)
		if (user != (uid_t) -1 && !uid_eq(key->uid, uid))
			goto error_put;

		/* only the sysadmin can set the key's GID to a group other
		 * than one of those that the current process subscribes to */
		if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid))
		if (group != (gid_t) -1 && !gid_eq(gid, key->gid) && !in_group_p(gid))
			goto error_put;
	}

	/* change the UID */
	if (uid != (uid_t) -1 && uid != key->uid) {
	if (user != (uid_t) -1 && !uid_eq(uid, key->uid)) {
		ret = -ENOMEM;
		newowner = key_user_lookup(uid, current_user_ns());
		newowner = key_user_lookup(uid);
		if (!newowner)
			goto error_put;

		/* transfer the quota burden to the new user */
		if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
			unsigned maxkeys = (uid == 0) ?
			unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ?
				key_quota_root_maxkeys : key_quota_maxkeys;
			unsigned maxbytes = (uid == 0) ?
			unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ?
				key_quota_root_maxbytes : key_quota_maxbytes;

			spin_lock(&newowner->lock);
@@ -846,7 +856,7 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
	}

	/* change the GID */
	if (gid != (gid_t) -1)
	if (group != (gid_t) -1)
		key->gid = gid;

	ret = 0;
@@ -897,7 +907,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
	down_write(&key->sem);

	/* if we're not the sysadmin, we can only change a key that we own */
	if (capable(CAP_SYS_ADMIN) || key->uid == current_fsuid()) {
	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
		key->perm = perm;
		ret = 0;
	}
@@ -1507,18 +1517,18 @@ long keyctl_session_to_parent(void)

	/* the parent must have the same effective ownership and mustn't be
	 * SUID/SGID */
	if (pcred->uid	!= mycred->euid	||
	    pcred->euid	!= mycred->euid	||
	    pcred->suid	!= mycred->euid	||
	    pcred->gid	!= mycred->egid	||
	    pcred->egid	!= mycred->egid	||
	    pcred->sgid	!= mycred->egid)
	if (!uid_eq(pcred->uid,	 mycred->euid) ||
	    !uid_eq(pcred->euid, mycred->euid) ||
	    !uid_eq(pcred->suid, mycred->euid) ||
	    !gid_eq(pcred->gid,	 mycred->egid) ||
	    !gid_eq(pcred->egid, mycred->egid) ||
	    !gid_eq(pcred->sgid, mycred->egid))
		goto unlock;

	/* the keyrings must have the same UID */
	if ((pcred->tgcred->session_keyring &&
	     pcred->tgcred->session_keyring->uid != mycred->euid) ||
	    mycred->tgcred->session_keyring->uid != mycred->euid)
	     !uid_eq(pcred->tgcred->session_keyring->uid, mycred->euid)) ||
	    !uid_eq(mycred->tgcred->session_keyring->uid, mycred->euid))
		goto unlock;

	/* cancel an already pending keyring replacement */
Loading