All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-09-26 23:05 ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-26 23:05 UTC (permalink / raw)
  To: linux-security-module

Hi Eric,

Here's a patch that partially implements ACLs on keys.  I haven't yet done
KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
been modified to deal with it.

Most of the keyutils testsuite passes, just the permissions tests that don't
because there isn't a fully compatible mapping from SETATTR -> ACL ->
DESCRIBE.

David
---
KEYS: Replace uid/gid/perm permissions checking with ACL

Replace the uid/gid/perm permissions checking on a key with an ACL to allow
the SETATTR permission to be split.  The problem is that SETATTR covers a
slew of things, not all of which should be grouped together.  This
includes:

 (1) Changing the key ownership.

 (2) Changing the security information.

 (3) Keyring restriction.

 (4) Expiry time.

 (5) Revocation.

and it has also been proposed to add:

 (6) Invalidation.

The above can be divided into three groups: Controlling access (1), (2) and
(3), managing the content at construction time (4) and managing the key (5)
and (6).


Add an ACL set & get functions that use a list of elements of the following
form:

        struct key_ace {
                union {
                        uid_t           uid;
                        gid_t           gid;
                        unsigned int    special_id;
                };
                unsigned int mask;
        };

where the top four bits of mask indicate the subject being checked:

	KEY_ACE_SPECIAL		A specific value
	KEY_ACE_UID		A nominated UID
	KEY_ACE_GID		A nominated GID

and if SPECIAL is specified, special_id is one of the following:

	KEY_ACE_EVERYONE	Everyone, including owner and group
	KEY_ACE_GROUP		The key's group
	KEY_ACE_OWNER		The owner of the key
	KEY_ACE_POSSESSOR	Any process that possesses the key
	KEY_ACE_ROOT		The user namespace root user
	KEY_ACE_SYS_ADMIN	Anyone with CAP_SYS_ADMIN
	KEY_ACE_NET_ADMIN	Anyone with CAP_NET_ADMIN

Each special ID may only occur once in an ACL.


The bottom 28 bits of mask indicate the permissions being granted:

	KEY_ACE_VIEW		Can view the key metadata
	KEY_ACE_READ		Can read the key content
	KEY_ACE_WRITE		Can update/modify the key content
	KEY_ACE_SEARCH		Can find the key by searching/requesting
	KEY_ACE_LINK		Can make a link to the key
	KEY_ACE_INVAL		Can invalidate
	KEY_ACE_REVOKE		Can revoke
	KEY_ACE_SET_SECURITY	Can set security
	KEY_ACE_JOIN		Can join this keyring
	KEY_ACE_CLEAR		Can clear this keyring

The ACEs are purely additive; all ACEs that apply are considered.  I figure
that it's unlikely that you'll give the key's Group permissions that you
wouldn't give the Owner and Other permissions that you wouldn't give to both
Group and Owner.  Possessor permissions are already additive.

The functions are:

        keyctl(KEYCTL_GET_ACL, key_serial_t key,
               struct key_ace *acl_buffer, unsigned int buflen);

        keyctl(KEYCTL_SET_ACL, key_serial_t key,
               const struct key_ace *acl, unsigned int len);

The KEYCTL_SETATTR function is then deprecated.  If called, it will
construct an ACL to reflect the mask it is given, using possessor, owner,
group and other ACE's as appropriate if any of those elements are granted
any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
will return an error if SETACL has been called on a key.

The KEYCTL_DESCRIBE function then creates a permissions mask to return
depending on possessor, owner, group and other ACEs, indicating SETATTR if
any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
of WRITE, REVOKE or CLEAR are set.

Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
the value set with KEYCTL_SETATTR - but this is already true because keys
that lack ->read() can't have READ set and keys that lack ->write() can't
have WRITE set.

The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
set, or if the caller has a valid instantiation auth token.

The KEYCTL_INVALIDATE function then requires INVAL.

The KEYCTL_REVOKE function then requires REVOKE.

The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
existing keyring.

The JOIN permission is enabled by default for session keyrings and manually
created keyrings only.

Signed-off-by: David Howells <dhowells@redhat.com>
---
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys  		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys  		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..0be132e3fb72 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* Anyone with user namespace root */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type = key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type = &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..eb7ed724f2db 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -295,7 +295,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -798,7 +799,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +890,13 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm = KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type = &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
+	/* If the caller doesn't provide, provide default permissions. */
+	if (!acl)
+		acl = &default_key_acl;
 
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type = &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..70b91b052041 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -493,7 +493,7 @@ static long keyring_read(const struct key *keyring,
  * Allocate a keyring and link into the destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +502,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..89259f8f6298 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace = 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,70 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace = 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & 0xe0)
+					perm |= KEY_POS_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & 0xe0)
+					perm |= KEY_USR_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & 0xe0)
+					perm |= KEY_GRP_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & 0xe0)
+					perm |= KEY_OTH_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id = KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) =
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry = 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring  				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) = -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type = &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret = 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm = 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-09-26 23:05 ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-26 23:05 UTC (permalink / raw)
  To: Eric Biggers; +Cc: David Howells, keyrings, linux-security-module, linux-kernel

Hi Eric,

Here's a patch that partially implements ACLs on keys.  I haven't yet done
KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
been modified to deal with it.

Most of the keyutils testsuite passes, just the permissions tests that don't
because there isn't a fully compatible mapping from SETATTR -> ACL ->
DESCRIBE.

David
---
KEYS: Replace uid/gid/perm permissions checking with ACL

Replace the uid/gid/perm permissions checking on a key with an ACL to allow
the SETATTR permission to be split.  The problem is that SETATTR covers a
slew of things, not all of which should be grouped together.  This
includes:

 (1) Changing the key ownership.

 (2) Changing the security information.

 (3) Keyring restriction.

 (4) Expiry time.

 (5) Revocation.

and it has also been proposed to add:

 (6) Invalidation.

The above can be divided into three groups: Controlling access (1), (2) and
(3), managing the content at construction time (4) and managing the key (5)
and (6).


Add an ACL set & get functions that use a list of elements of the following
form:

        struct key_ace {
                union {
                        uid_t           uid;
                        gid_t           gid;
                        unsigned int    special_id;
                };
                unsigned int mask;
        };

where the top four bits of mask indicate the subject being checked:

	KEY_ACE_SPECIAL		A specific value
	KEY_ACE_UID		A nominated UID
	KEY_ACE_GID		A nominated GID

and if SPECIAL is specified, special_id is one of the following:

	KEY_ACE_EVERYONE	Everyone, including owner and group
	KEY_ACE_GROUP		The key's group
	KEY_ACE_OWNER		The owner of the key
	KEY_ACE_POSSESSOR	Any process that possesses the key
	KEY_ACE_ROOT		The user namespace root user
	KEY_ACE_SYS_ADMIN	Anyone with CAP_SYS_ADMIN
	KEY_ACE_NET_ADMIN	Anyone with CAP_NET_ADMIN

Each special ID may only occur once in an ACL.


The bottom 28 bits of mask indicate the permissions being granted:

	KEY_ACE_VIEW		Can view the key metadata
	KEY_ACE_READ		Can read the key content
	KEY_ACE_WRITE		Can update/modify the key content
	KEY_ACE_SEARCH		Can find the key by searching/requesting
	KEY_ACE_LINK		Can make a link to the key
	KEY_ACE_INVAL		Can invalidate
	KEY_ACE_REVOKE		Can revoke
	KEY_ACE_SET_SECURITY	Can set security
	KEY_ACE_JOIN		Can join this keyring
	KEY_ACE_CLEAR		Can clear this keyring

The ACEs are purely additive; all ACEs that apply are considered.  I figure
that it's unlikely that you'll give the key's Group permissions that you
wouldn't give the Owner and Other permissions that you wouldn't give to both
Group and Owner.  Possessor permissions are already additive.

The functions are:

        keyctl(KEYCTL_GET_ACL, key_serial_t key,
               struct key_ace *acl_buffer, unsigned int buflen);

        keyctl(KEYCTL_SET_ACL, key_serial_t key,
               const struct key_ace *acl, unsigned int len);

The KEYCTL_SETATTR function is then deprecated.  If called, it will
construct an ACL to reflect the mask it is given, using possessor, owner,
group and other ACE's as appropriate if any of those elements are granted
any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
will return an error if SETACL has been called on a key.

The KEYCTL_DESCRIBE function then creates a permissions mask to return
depending on possessor, owner, group and other ACEs, indicating SETATTR if
any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
of WRITE, REVOKE or CLEAR are set.

Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
the value set with KEYCTL_SETATTR - but this is already true because keys
that lack ->read() can't have READ set and keys that lack ->write() can't
have WRITE set.

The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
set, or if the caller has a valid instantiation auth token.

The KEYCTL_INVALIDATE function then requires INVAL.

The KEYCTL_REVOKE function then requires REVOKE.

The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
existing keyring.

The JOIN permission is enabled by default for session keyrings and manually
created keyrings only.

Signed-off-by: David Howells <dhowells@redhat.com>
---
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys =
 		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys =
 		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..0be132e3fb72 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* Anyone with user namespace root */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type == key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type == &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..eb7ed724f2db 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -295,7 +295,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -798,7 +799,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +890,13 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm == KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type == &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
+	/* If the caller doesn't provide, provide default permissions. */
+	if (!acl)
+		acl = &default_key_acl;
 
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type == &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..70b91b052041 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -493,7 +493,7 @@ static long keyring_read(const struct key *keyring,
  * Allocate a keyring and link into the destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +502,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..89259f8f6298 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace == 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,70 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace == 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & 0xe0)
+					perm |= KEY_POS_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & 0xe0)
+					perm |= KEY_USR_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & 0xe0)
+					perm |= KEY_GRP_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & 0xe0)
+					perm |= KEY_OTH_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id == KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry == 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring =
 				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) == -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type == &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret == 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm == 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-09-26 23:05 ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-26 23:05 UTC (permalink / raw)
  To: linux-security-module

Hi Eric,

Here's a patch that partially implements ACLs on keys.  I haven't yet done
KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
been modified to deal with it.

Most of the keyutils testsuite passes, just the permissions tests that don't
because there isn't a fully compatible mapping from SETATTR -> ACL ->
DESCRIBE.

David
---
KEYS: Replace uid/gid/perm permissions checking with ACL

Replace the uid/gid/perm permissions checking on a key with an ACL to allow
the SETATTR permission to be split.  The problem is that SETATTR covers a
slew of things, not all of which should be grouped together.  This
includes:

 (1) Changing the key ownership.

 (2) Changing the security information.

 (3) Keyring restriction.

 (4) Expiry time.

 (5) Revocation.

and it has also been proposed to add:

 (6) Invalidation.

The above can be divided into three groups: Controlling access (1), (2) and
(3), managing the content at construction time (4) and managing the key (5)
and (6).


Add an ACL set & get functions that use a list of elements of the following
form:

        struct key_ace {
                union {
                        uid_t           uid;
                        gid_t           gid;
                        unsigned int    special_id;
                };
                unsigned int mask;
        };

where the top four bits of mask indicate the subject being checked:

	KEY_ACE_SPECIAL		A specific value
	KEY_ACE_UID		A nominated UID
	KEY_ACE_GID		A nominated GID

and if SPECIAL is specified, special_id is one of the following:

	KEY_ACE_EVERYONE	Everyone, including owner and group
	KEY_ACE_GROUP		The key's group
	KEY_ACE_OWNER		The owner of the key
	KEY_ACE_POSSESSOR	Any process that possesses the key
	KEY_ACE_ROOT		The user namespace root user
	KEY_ACE_SYS_ADMIN	Anyone with CAP_SYS_ADMIN
	KEY_ACE_NET_ADMIN	Anyone with CAP_NET_ADMIN

Each special ID may only occur once in an ACL.


The bottom 28 bits of mask indicate the permissions being granted:

	KEY_ACE_VIEW		Can view the key metadata
	KEY_ACE_READ		Can read the key content
	KEY_ACE_WRITE		Can update/modify the key content
	KEY_ACE_SEARCH		Can find the key by searching/requesting
	KEY_ACE_LINK		Can make a link to the key
	KEY_ACE_INVAL		Can invalidate
	KEY_ACE_REVOKE		Can revoke
	KEY_ACE_SET_SECURITY	Can set security
	KEY_ACE_JOIN		Can join this keyring
	KEY_ACE_CLEAR		Can clear this keyring

The ACEs are purely additive; all ACEs that apply are considered.  I figure
that it's unlikely that you'll give the key's Group permissions that you
wouldn't give the Owner and Other permissions that you wouldn't give to both
Group and Owner.  Possessor permissions are already additive.

The functions are:

        keyctl(KEYCTL_GET_ACL, key_serial_t key,
               struct key_ace *acl_buffer, unsigned int buflen);

        keyctl(KEYCTL_SET_ACL, key_serial_t key,
               const struct key_ace *acl, unsigned int len);

The KEYCTL_SETATTR function is then deprecated.  If called, it will
construct an ACL to reflect the mask it is given, using possessor, owner,
group and other ACE's as appropriate if any of those elements are granted
any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
will return an error if SETACL has been called on a key.

The KEYCTL_DESCRIBE function then creates a permissions mask to return
depending on possessor, owner, group and other ACEs, indicating SETATTR if
any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
of WRITE, REVOKE or CLEAR are set.

Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
the value set with KEYCTL_SETATTR - but this is already true because keys
that lack ->read() can't have READ set and keys that lack ->write() can't
have WRITE set.

The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
set, or if the caller has a valid instantiation auth token.

The KEYCTL_INVALIDATE function then requires INVAL.

The KEYCTL_REVOKE function then requires REVOKE.

The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
existing keyring.

The JOIN permission is enabled by default for session keyrings and manually
created keyrings only.

Signed-off-by: David Howells <dhowells@redhat.com>
---
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys =
 		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys =
 		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..0be132e3fb72 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* Anyone with user namespace root */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type == key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type == &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..eb7ed724f2db 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -295,7 +295,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -798,7 +799,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +890,13 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm == KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type == &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
+	/* If the caller doesn't provide, provide default permissions. */
+	if (!acl)
+		acl = &default_key_acl;
 
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type == &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..70b91b052041 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -493,7 +493,7 @@ static long keyring_read(const struct key *keyring,
  * Allocate a keyring and link into the destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +502,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..89259f8f6298 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace == 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,70 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace == 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & 0xe0)
+					perm |= KEY_POS_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & 0xe0)
+					perm |= KEY_USR_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & 0xe0)
+					perm |= KEY_GRP_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & 0xe0)
+					perm |= KEY_OTH_SETATTR;
+				if (mask & (KEY_ACE_REVOKE | KEY_ACE_CLEAR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id == KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry == 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring =
 				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) == -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type == &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret == 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm == 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
  2017-09-26 23:05 ` David Howells
  (?)
@ 2017-09-27 11:41   ` David Howells
  -1 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 11:41 UTC (permalink / raw)
  To: linux-security-module

David Howells <dhowells@redhat.com> wrote:

> Here's a patch that partially implements ACLs on keys.  I haven't yet done
> KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
> been modified to deal with it.
> 
> Most of the keyutils testsuite passes, just the permissions tests that don't
> because there isn't a fully compatible mapping from SETATTR -> ACL ->
> DESCRIBE.

Here's an updated patch.  It still doesn't implement GET/SET_ACL, but it
passes almost all the tests in the keyutils testsuite, barring:

 (1) tests/keyctl/dh_compute/badargs: The first wrong-key-type test now
     returns EOPNOTSUPP rather than ENOKEY as READ permission isn't removed
     if the type doesn't have ->read().  You still can't actually read the
     key.

 (2) tests/keyctl/permitting/valid: The view-other-permissions test doesn't
     work as Other has been replaced with Everyone in the ACL.

All other tests still work.
---
commit 2b5fa6ac075006f129b376886a5e8b41d47efad1
Author: David Howells <dhowells@redhat.com>
Date:   Tue Sep 26 09:45:26 2017 +0100

    KEYS: Replace uid/gid/perm permissions checking with ACL
    
    Replace the uid/gid/perm permissions checking on a key with an ACL to allow
    the SETATTR permission to be split.  The problem is that SETATTR covers a
    slew of things, not all of which should be grouped together.  This
    includes:
    
     (1) Changing the key ownership.
    
     (2) Changing the security information.
    
     (3) Keyring restriction.
    
     (4) Expiry time.
    
     (5) Revocation.
    
    and it has also been proposed to add:
    
     (6) Invalidation.
    
    The above can be divided into three groups: Controlling access (1), (2) and
    (3), managing the content at construction time (4) and managing the key (5)
    and (6).
    
    
    Add an ACL set & get functions that use a list of elements of the following
    form:
    
            struct key_ace {
                    union {
                            uid_t           uid;
                            gid_t           gid;
                            unsigned int    special_id;
                    };
                    unsigned int mask;
            };
    
    where the top four bits of mask indicate the subject being checked:
    
            KEY_ACE_SPECIAL         A specific value
            KEY_ACE_UID             A nominated UID
            KEY_ACE_GID             A nominated GID
    
    and if SPECIAL is specified, special_id is one of the following:
    
            KEY_ACE_POSSESSOR       The possessor of the key
            KEY_ACE_OWNER           The owner of the key
            KEY_ACE_GROUP           The key's group
            KEY_ACE_OTHER           Everyone else
    
    Each special ID may only occur once in an ACL.
    
    The bottom 28 bits of mask indicate the permissions being granted:
    
            KEY_ACE_VIEW            Can view the key metadata
            KEY_ACE_READ            Can read the key content
            KEY_ACE_WRITE           Can update/modify the key content
            KEY_ACE_SEARCH          Can find the key by searching/requesting
            KEY_ACE_LINK            Can make a link to the key
            KEY_ACE_INVAL           Can invalidate
            KEY_ACE_REVOKE          Can revoke
            KEY_ACE_SET_SECURITY    Can set security
            KEY_ACE_JOIN            Can join this keyring
            KEY_ACE_CLEAR           Can clear this keyring
    
    The ACEs are purely additive; all ACEs that apply are considered.  I figure
    that it's unlikely that you'll give the key's Group permissions that you
    wouldn't give the Owner and Other permissions that you wouldn't give to both
    Group and Owner.  Possessor permissions are already additive.
    
    The functions are:
    
            keyctl(KEYCTL_GET_ACL, key_serial_t key,
                   struct key_ace *acl_buffer, unsigned int buflen);
    
            keyctl(KEYCTL_SET_ACL, key_serial_t key,
                   const struct key_ace *acl, unsigned int len);
    
    The KEYCTL_SETATTR function is then deprecated.  If called, it will
    construct an ACL to reflect the mask it is given, using possessor, owner,
    group and other ACE's as appropriate if any of those elements are granted
    any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
    SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
    CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
    will return an error if SETACL has been called on a key.
    
    The KEYCTL_DESCRIBE function then creates a permissions mask to return
    depending on possessor, owner, group and other ACEs, indicating SETATTR if
    any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
    of WRITE, REVOKE or CLEAR are set.
    
    Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
    the value set with KEYCTL_SETATTR - but this is already true because keys
    that lack ->read() can't have READ set and keys that lack ->write() can't
    have WRITE set.
    
    The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
    set, or if the caller has a valid instantiation auth token.
    
    The KEYCTL_INVALIDATE function then requires INVAL.
    
    The KEYCTL_REVOKE function then requires REVOKE.
    
    The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
    existing keyring.
    
    The JOIN permission is enabled by default for session keyrings and manually
    created keyrings only.
    
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys  		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys  		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..8a9e829ef499 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type = key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type = &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..f342bf586006 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -199,7 +199,7 @@ static inline void key_alloc_serial(struct key *key)
  * @uid: The owner of the new key.
  * @gid: The group ID for the new key's group permissions.
  * @cred: The credentials specifying UID namespace.
- * @perm: The permissions mask of the new key.
+ * @acl: The ACL to attach to the new key.
  * @flags: Flags specifying quota properties.
  * @restrict_link: Optional link restriction for new keyrings.
  *
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -250,6 +250,9 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	desclen = strlen(desc);
 	quotalen = desclen + 1 + type->def_datalen;
 
+	if (!acl)
+		acl = &default_key_acl;
+
 	/* get hold of the key tracking for this user */
 	user = key_user_lookup(uid);
 	if (!user)
@@ -295,7 +298,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -775,7 +779,7 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
  * @description: The searchable description for the key.
  * @payload: The data to use to instantiate or update the key.
  * @plen: The length of @payload.
- * @perm: The permissions mask for a new key.
+ * @acl: The ACL to attach if a key is created.
  * @flags: The quota flags for a new key.
  *
  * Search the destination keyring for a key of the same description and if one
@@ -798,7 +802,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +893,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm = KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type = &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
-
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type = &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..ea7e2620ca03 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -489,11 +489,19 @@ static long keyring_read(const struct key *keyring,
 	return ctx.count;
 }
 
-/*
- * Allocate a keyring and link into the destination keyring.
+/**
+ * keyring_alloc - Allocate a keyring and link into the destination
+ * @description: The key description to allow the key to be searched out.
+ * @uid: The owner of the new key.
+ * @gid: The group ID for the new key's group permissions.
+ * @cred: The credentials specifying UID namespace.
+ * @acl: The ACL to attach to the new key.
+ * @flags: Flags specifying quota properties.
+ * @restrict_link: Optional link restriction for new keyrings.
+ * @dest: Destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +510,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..5e186b0f802d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace = 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,80 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace = 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_POS_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_POS_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_USR_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_USR_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_GRP_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_GRP_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_OTH_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_OTH_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+
+		//printk("[%d] %08x %08x %08x\n", i, ace->special_id, ace->mask, perm);
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index d0cb5b32eff7..0410a196a8f7 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -16,6 +16,33 @@
 
 unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
 
+static struct key_acl persistent_register_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl persistent_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE__ORDINARY | KEY_ACE_CLEAR |
+					   KEY_ACE_INVAL),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Create the persistent keyring register for the current user namespace.
  *
@@ -26,8 +53,7 @@ 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),
+					&persistent_register_keyring_acl,
 					KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(reg))
 		return PTR_ERR(reg);
@@ -60,8 +86,7 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid,
 
 	persistent = keyring_alloc(index_key->description,
 				   uid, INVALID_GID, current_cred(),
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW | KEY_USR_READ),
+				   &persistent_keyring_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA, NULL,
 				   ns->persistent_keyring_register);
 	if (IS_ERR(persistent))
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id = KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) =
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry = 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring  				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) = -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type = &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret = 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..d810a54070f7 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -29,6 +29,19 @@ static void request_key_auth_revoke(struct key *);
 static void request_key_auth_destroy(struct key *);
 static long request_key_auth_read(const struct key *, char __user *, size_t);
 
+static struct key_acl request_key_auth_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The request-key authorisation key type definition.
  */
@@ -203,8 +216,8 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
 
 	authkey = key_alloc(&key_type_request_key_auth, desc,
 			    cred->fsuid, cred->fsgid, cred,
-			    KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
-			    KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			    &request_key_auth_acl,
+			    KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(authkey)) {
 		ret = PTR_ERR(authkey);
 		goto error_free_rka;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm = 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-09-27 11:41   ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 11:41 UTC (permalink / raw)
  To: Eric Biggers; +Cc: dhowells, keyrings, linux-security-module, linux-kernel

David Howells <dhowells@redhat.com> wrote:

> Here's a patch that partially implements ACLs on keys.  I haven't yet done
> KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
> been modified to deal with it.
> 
> Most of the keyutils testsuite passes, just the permissions tests that don't
> because there isn't a fully compatible mapping from SETATTR -> ACL ->
> DESCRIBE.

Here's an updated patch.  It still doesn't implement GET/SET_ACL, but it
passes almost all the tests in the keyutils testsuite, barring:

 (1) tests/keyctl/dh_compute/badargs: The first wrong-key-type test now
     returns EOPNOTSUPP rather than ENOKEY as READ permission isn't removed
     if the type doesn't have ->read().  You still can't actually read the
     key.

 (2) tests/keyctl/permitting/valid: The view-other-permissions test doesn't
     work as Other has been replaced with Everyone in the ACL.

All other tests still work.
---
commit 2b5fa6ac075006f129b376886a5e8b41d47efad1
Author: David Howells <dhowells@redhat.com>
Date:   Tue Sep 26 09:45:26 2017 +0100

    KEYS: Replace uid/gid/perm permissions checking with ACL
    
    Replace the uid/gid/perm permissions checking on a key with an ACL to allow
    the SETATTR permission to be split.  The problem is that SETATTR covers a
    slew of things, not all of which should be grouped together.  This
    includes:
    
     (1) Changing the key ownership.
    
     (2) Changing the security information.
    
     (3) Keyring restriction.
    
     (4) Expiry time.
    
     (5) Revocation.
    
    and it has also been proposed to add:
    
     (6) Invalidation.
    
    The above can be divided into three groups: Controlling access (1), (2) and
    (3), managing the content at construction time (4) and managing the key (5)
    and (6).
    
    
    Add an ACL set & get functions that use a list of elements of the following
    form:
    
            struct key_ace {
                    union {
                            uid_t           uid;
                            gid_t           gid;
                            unsigned int    special_id;
                    };
                    unsigned int mask;
            };
    
    where the top four bits of mask indicate the subject being checked:
    
            KEY_ACE_SPECIAL         A specific value
            KEY_ACE_UID             A nominated UID
            KEY_ACE_GID             A nominated GID
    
    and if SPECIAL is specified, special_id is one of the following:
    
            KEY_ACE_POSSESSOR       The possessor of the key
            KEY_ACE_OWNER           The owner of the key
            KEY_ACE_GROUP           The key's group
            KEY_ACE_OTHER           Everyone else
    
    Each special ID may only occur once in an ACL.
    
    The bottom 28 bits of mask indicate the permissions being granted:
    
            KEY_ACE_VIEW            Can view the key metadata
            KEY_ACE_READ            Can read the key content
            KEY_ACE_WRITE           Can update/modify the key content
            KEY_ACE_SEARCH          Can find the key by searching/requesting
            KEY_ACE_LINK            Can make a link to the key
            KEY_ACE_INVAL           Can invalidate
            KEY_ACE_REVOKE          Can revoke
            KEY_ACE_SET_SECURITY    Can set security
            KEY_ACE_JOIN            Can join this keyring
            KEY_ACE_CLEAR           Can clear this keyring
    
    The ACEs are purely additive; all ACEs that apply are considered.  I figure
    that it's unlikely that you'll give the key's Group permissions that you
    wouldn't give the Owner and Other permissions that you wouldn't give to both
    Group and Owner.  Possessor permissions are already additive.
    
    The functions are:
    
            keyctl(KEYCTL_GET_ACL, key_serial_t key,
                   struct key_ace *acl_buffer, unsigned int buflen);
    
            keyctl(KEYCTL_SET_ACL, key_serial_t key,
                   const struct key_ace *acl, unsigned int len);
    
    The KEYCTL_SETATTR function is then deprecated.  If called, it will
    construct an ACL to reflect the mask it is given, using possessor, owner,
    group and other ACE's as appropriate if any of those elements are granted
    any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
    SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
    CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
    will return an error if SETACL has been called on a key.
    
    The KEYCTL_DESCRIBE function then creates a permissions mask to return
    depending on possessor, owner, group and other ACEs, indicating SETATTR if
    any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
    of WRITE, REVOKE or CLEAR are set.
    
    Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
    the value set with KEYCTL_SETATTR - but this is already true because keys
    that lack ->read() can't have READ set and keys that lack ->write() can't
    have WRITE set.
    
    The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
    set, or if the caller has a valid instantiation auth token.
    
    The KEYCTL_INVALIDATE function then requires INVAL.
    
    The KEYCTL_REVOKE function then requires REVOKE.
    
    The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
    existing keyring.
    
    The JOIN permission is enabled by default for session keyrings and manually
    created keyrings only.
    
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys =
 		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys =
 		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..8a9e829ef499 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type == key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type == &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..f342bf586006 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -199,7 +199,7 @@ static inline void key_alloc_serial(struct key *key)
  * @uid: The owner of the new key.
  * @gid: The group ID for the new key's group permissions.
  * @cred: The credentials specifying UID namespace.
- * @perm: The permissions mask of the new key.
+ * @acl: The ACL to attach to the new key.
  * @flags: Flags specifying quota properties.
  * @restrict_link: Optional link restriction for new keyrings.
  *
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -250,6 +250,9 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	desclen = strlen(desc);
 	quotalen = desclen + 1 + type->def_datalen;
 
+	if (!acl)
+		acl = &default_key_acl;
+
 	/* get hold of the key tracking for this user */
 	user = key_user_lookup(uid);
 	if (!user)
@@ -295,7 +298,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -775,7 +779,7 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
  * @description: The searchable description for the key.
  * @payload: The data to use to instantiate or update the key.
  * @plen: The length of @payload.
- * @perm: The permissions mask for a new key.
+ * @acl: The ACL to attach if a key is created.
  * @flags: The quota flags for a new key.
  *
  * Search the destination keyring for a key of the same description and if one
@@ -798,7 +802,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +893,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm == KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type == &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
-
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type == &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..ea7e2620ca03 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -489,11 +489,19 @@ static long keyring_read(const struct key *keyring,
 	return ctx.count;
 }
 
-/*
- * Allocate a keyring and link into the destination keyring.
+/**
+ * keyring_alloc - Allocate a keyring and link into the destination
+ * @description: The key description to allow the key to be searched out.
+ * @uid: The owner of the new key.
+ * @gid: The group ID for the new key's group permissions.
+ * @cred: The credentials specifying UID namespace.
+ * @acl: The ACL to attach to the new key.
+ * @flags: Flags specifying quota properties.
+ * @restrict_link: Optional link restriction for new keyrings.
+ * @dest: Destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +510,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..5e186b0f802d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace == 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,80 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace == 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_POS_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_POS_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_USR_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_USR_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_GRP_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_GRP_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_OTH_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_OTH_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+
+		//printk("[%d] %08x %08x %08x\n", i, ace->special_id, ace->mask, perm);
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index d0cb5b32eff7..0410a196a8f7 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -16,6 +16,33 @@
 
 unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
 
+static struct key_acl persistent_register_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl persistent_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE__ORDINARY | KEY_ACE_CLEAR |
+					   KEY_ACE_INVAL),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Create the persistent keyring register for the current user namespace.
  *
@@ -26,8 +53,7 @@ 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),
+					&persistent_register_keyring_acl,
 					KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(reg))
 		return PTR_ERR(reg);
@@ -60,8 +86,7 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid,
 
 	persistent = keyring_alloc(index_key->description,
 				   uid, INVALID_GID, current_cred(),
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW | KEY_USR_READ),
+				   &persistent_keyring_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA, NULL,
 				   ns->persistent_keyring_register);
 	if (IS_ERR(persistent))
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id == KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry == 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring =
 				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) == -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type == &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret == 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..d810a54070f7 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -29,6 +29,19 @@ static void request_key_auth_revoke(struct key *);
 static void request_key_auth_destroy(struct key *);
 static long request_key_auth_read(const struct key *, char __user *, size_t);
 
+static struct key_acl request_key_auth_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The request-key authorisation key type definition.
  */
@@ -203,8 +216,8 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
 
 	authkey = key_alloc(&key_type_request_key_auth, desc,
 			    cred->fsuid, cred->fsgid, cred,
-			    KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
-			    KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			    &request_key_auth_acl,
+			    KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(authkey)) {
 		ret = PTR_ERR(authkey);
 		goto error_free_rka;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm == 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-09-27 11:41   ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 11:41 UTC (permalink / raw)
  To: linux-security-module

David Howells <dhowells@redhat.com> wrote:

> Here's a patch that partially implements ACLs on keys.  I haven't yet done
> KEYCTL_[GS]ET_ACL, but KEYCTL_SETATTR, KEYCTL_DESCRIBE and /proc/keys have
> been modified to deal with it.
> 
> Most of the keyutils testsuite passes, just the permissions tests that don't
> because there isn't a fully compatible mapping from SETATTR -> ACL ->
> DESCRIBE.

Here's an updated patch.  It still doesn't implement GET/SET_ACL, but it
passes almost all the tests in the keyutils testsuite, barring:

 (1) tests/keyctl/dh_compute/badargs: The first wrong-key-type test now
     returns EOPNOTSUPP rather than ENOKEY as READ permission isn't removed
     if the type doesn't have ->read().  You still can't actually read the
     key.

 (2) tests/keyctl/permitting/valid: The view-other-permissions test doesn't
     work as Other has been replaced with Everyone in the ACL.

All other tests still work.
---
commit 2b5fa6ac075006f129b376886a5e8b41d47efad1
Author: David Howells <dhowells@redhat.com>
Date:   Tue Sep 26 09:45:26 2017 +0100

    KEYS: Replace uid/gid/perm permissions checking with ACL
    
    Replace the uid/gid/perm permissions checking on a key with an ACL to allow
    the SETATTR permission to be split.  The problem is that SETATTR covers a
    slew of things, not all of which should be grouped together.  This
    includes:
    
     (1) Changing the key ownership.
    
     (2) Changing the security information.
    
     (3) Keyring restriction.
    
     (4) Expiry time.
    
     (5) Revocation.
    
    and it has also been proposed to add:
    
     (6) Invalidation.
    
    The above can be divided into three groups: Controlling access (1), (2) and
    (3), managing the content at construction time (4) and managing the key (5)
    and (6).
    
    
    Add an ACL set & get functions that use a list of elements of the following
    form:
    
            struct key_ace {
                    union {
                            uid_t           uid;
                            gid_t           gid;
                            unsigned int    special_id;
                    };
                    unsigned int mask;
            };
    
    where the top four bits of mask indicate the subject being checked:
    
            KEY_ACE_SPECIAL         A specific value
            KEY_ACE_UID             A nominated UID
            KEY_ACE_GID             A nominated GID
    
    and if SPECIAL is specified, special_id is one of the following:
    
            KEY_ACE_POSSESSOR       The possessor of the key
            KEY_ACE_OWNER           The owner of the key
            KEY_ACE_GROUP           The key's group
            KEY_ACE_OTHER           Everyone else
    
    Each special ID may only occur once in an ACL.
    
    The bottom 28 bits of mask indicate the permissions being granted:
    
            KEY_ACE_VIEW            Can view the key metadata
            KEY_ACE_READ            Can read the key content
            KEY_ACE_WRITE           Can update/modify the key content
            KEY_ACE_SEARCH          Can find the key by searching/requesting
            KEY_ACE_LINK            Can make a link to the key
            KEY_ACE_INVAL           Can invalidate
            KEY_ACE_REVOKE          Can revoke
            KEY_ACE_SET_SECURITY    Can set security
            KEY_ACE_JOIN            Can join this keyring
            KEY_ACE_CLEAR           Can clear this keyring
    
    The ACEs are purely additive; all ACEs that apply are considered.  I figure
    that it's unlikely that you'll give the key's Group permissions that you
    wouldn't give the Owner and Other permissions that you wouldn't give to both
    Group and Owner.  Possessor permissions are already additive.
    
    The functions are:
    
            keyctl(KEYCTL_GET_ACL, key_serial_t key,
                   struct key_ace *acl_buffer, unsigned int buflen);
    
            keyctl(KEYCTL_SET_ACL, key_serial_t key,
                   const struct key_ace *acl, unsigned int len);
    
    The KEYCTL_SETATTR function is then deprecated.  If called, it will
    construct an ACL to reflect the mask it is given, using possessor, owner,
    group and other ACE's as appropriate if any of those elements are granted
    any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
    SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
    CLEAR.  JOIN is turned on if a keyring is being altered.  KEYCTL_SETATTR
    will return an error if SETACL has been called on a key.
    
    The KEYCTL_DESCRIBE function then creates a permissions mask to return
    depending on possessor, owner, group and other ACEs, indicating SETATTR if
    any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
    of WRITE, REVOKE or CLEAR are set.
    
    Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
    the value set with KEYCTL_SETATTR - but this is already true because keys
    that lack ->read() can't have READ set and keys that lack ->write() can't
    have WRITE set.
    
    The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
    set, or if the caller has a valid instantiation auth token.
    
    The KEYCTL_INVALIDATE function then requires INVAL.
    
    The KEYCTL_REVOKE function then requires REVOKE.
    
    The KEYCTL_JOIN_SESSION_KEYRING function then requires JOIN to join an
    existing keyring.
    
    The JOIN permission is enabled by default for session keyrings and manually
    created keyrings only.
    
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..5f03a60ebb7f 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -23,6 +23,32 @@
 
 static struct key *blacklist_keyring;
 
+static struct key_acl blacklist_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl blacklist_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The description must be a type prefix, a colon and then an even number of
  * hex digits.  The hash is kept in the description.
@@ -93,8 +119,7 @@ int mark_hash_blacklisted(const char *hash)
 				   hash,
 				   NULL,
 				   0,
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW),
+				   &blacklist_key_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA |
 				   KEY_ALLOC_BUILT_IN);
 	if (IS_ERR(key)) {
@@ -153,9 +178,7 @@ static int __init blacklist_init(void)
 		keyring_alloc(".blacklist",
 			      KUIDT_INIT(0), KGIDT_INIT(0),
 			      current_cred(),
-			      (KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ |
-			      KEY_USR_SEARCH,
+			      &blacklist_keyring_acl,
 			      KEY_ALLOC_NOT_IN_QUOTA |
 			      KEY_FLAG_KEEP,
 			      NULL, NULL);
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..1516598377a5 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -27,6 +27,33 @@ static struct key *secondary_trusted_keys;
 extern __initconst const u8 system_certificate_list[];
 extern __initconst const unsigned long system_certificate_list_size;
 
+static struct key_acl trusted_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl trusted_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = (KEY_ACE_SPECIAL |
+			 KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | KEY_ACE_WRITE),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA
  *
@@ -99,9 +126,7 @@ static __init int system_trusted_keyring_init(void)
 	builtin_trusted_keys =
 		keyring_alloc(".builtin_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_key_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      NULL, NULL);
 	if (IS_ERR(builtin_trusted_keys))
 		panic("Can't allocate builtin trusted keyring\n");
@@ -110,10 +135,7 @@ static __init int system_trusted_keyring_init(void)
 	secondary_trusted_keys =
 		keyring_alloc(".secondary_trusted_keys",
 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
-			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-			       KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH |
-			       KEY_USR_WRITE),
-			      KEY_ALLOC_NOT_IN_QUOTA,
+			      &trusted_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA,
 			      get_builtin_and_secondary_restriction(),
 			      NULL);
 	if (IS_ERR(secondary_trusted_keys))
@@ -163,8 +185,7 @@ static __init int load_system_certificate_list(void)
 					   NULL,
 					   p,
 					   plen,
-					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-					   KEY_USR_VIEW | KEY_USR_READ),
+					   &trusted_key_acl,
 					   KEY_ALLOC_NOT_IN_QUOTA |
 					   KEY_ALLOC_BUILT_IN |
 					   KEY_ALLOC_BYPASS_RESTRICTION);
diff --git a/fs/afs/security.c b/fs/afs/security.c
index faca66227ecf..9131c2032d3f 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell)
 
 	_debug("key %s", cell->anonymous_key->description);
 	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
-			  NULL);
+			  NULL, NULL);
 	if (IS_ERR(key)) {
 		if (PTR_ERR(key) != -ENOKEY) {
 			_leave(" = %ld", PTR_ERR(key));
diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c
index b611fc2e8984..0ad45f5fac59 100644
--- a/fs/cifs/cifs_spnego.c
+++ b/fs/cifs/cifs_spnego.c
@@ -32,6 +32,40 @@
 #include "cifsproto.h"
 static const struct cred *spnego_cred;
 
+static struct key_acl cifs_spnego_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_spnego_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* create a new cifs key */
 static int
 cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
@@ -168,7 +202,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
 
 	cifs_dbg(FYI, "key description = %s\n", description);
 	saved_cred = override_creds(spnego_cred);
-	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+	spnego_key = request_key(&cifs_spnego_key_type, description, "",
+				 &cifs_spnego_key_acl);
 	revert_creds(saved_cred);
 
 #ifdef CONFIG_CIFS_DEBUG2
@@ -205,8 +240,7 @@ init_cifs_spnego(void)
 
 	keyring = keyring_alloc(".cifs_spnego",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_spnego_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -221,7 +255,6 @@ init_cifs_spnego(void)
 	 * instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up
 	 */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	spnego_cred = cred;
diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c
index b98436f5c7c7..972f82601790 100644
--- a/fs/cifs/cifsacl.c
+++ b/fs/cifs/cifsacl.c
@@ -33,6 +33,40 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static struct key_acl cifs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl cifs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /* security id for everyone/world system group */
 static const struct cifs_sid sid_everyone = {
 	1, 1, {0, 0, 0, 0, 0, 1}, {0} };
@@ -298,7 +332,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid)
 
 	rc = 0;
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, desc, "");
+	sidkey = request_key(&cifs_idmap_key_type, desc, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n",
@@ -403,7 +438,8 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
 		return -ENOMEM;
 
 	saved_cred = override_creds(root_cred);
-	sidkey = request_key(&cifs_idmap_key_type, sidstr, "");
+	sidkey = request_key(&cifs_idmap_key_type, sidstr, "",
+			     &cifs_idmap_key_acl);
 	if (IS_ERR(sidkey)) {
 		rc = -EINVAL;
 		cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n",
@@ -481,8 +517,7 @@ init_cifs_idmap(void)
 
 	keyring = keyring_alloc(".cifs_idmap",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&cifs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -495,7 +530,6 @@ init_cifs_idmap(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	root_cred = cred;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 5aa2d278ca84..10ced2b32291 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2469,7 +2469,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 	}
 
 	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-	key = request_key(&key_type_logon, desc, "");
+	key = request_key(&key_type_logon, desc, "", NULL);
 	if (IS_ERR(key)) {
 		if (!ses->domainName) {
 			cifs_dbg(FYI, "domainName is NULL\n");
@@ -2480,7 +2480,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
 		/* didn't work, try to find a domain key */
 		sprintf(desc, "cifs:d:%s", ses->domainName);
 		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
-		key = request_key(&key_type_logon, desc, "");
+		key = request_key(&key_type_logon, desc, "", NULL);
 		if (IS_ERR(key)) {
 			rc = PTR_ERR(key);
 			goto out_err;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..c8b259ca1b92 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -323,7 +323,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	const char *buf;
 	int len;
 
-	key = request_key(&key_type_user, "fscache:objlist", NULL);
+	key = request_key(&key_type_user, "fscache:objlist", NULL, NULL);
 	if (IS_ERR(key))
 		goto no_config;
 
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index dd5d27da8c0c..d87e50cec1fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -70,6 +70,40 @@ struct idmap {
 	struct mutex		idmap_mutex;
 };
 
+static struct key_acl nfs_idmap_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
+static struct key_acl nfs_idmap_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * nfs_fattr_init_names - initialise the nfs_fattr owner_name/group_name fields
  * @fattr: fully initialised struct nfs_fattr
@@ -199,8 +233,7 @@ int nfs_idmap_init(void)
 
 	keyring = keyring_alloc(".id_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&nfs_idmap_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -215,7 +248,6 @@ int nfs_idmap_init(void)
 	if (ret < 0)
 		goto failed_reg_legacy;
 
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	id_resolver_cache = cred;
@@ -277,15 +309,14 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen,
 	if (ret <= 0)
 		return ERR_PTR(ret);
 
-	rkey = request_key(&key_type_id_resolver, desc, "");
+	rkey = request_key(&key_type_id_resolver, desc, "", &nfs_idmap_key_acl);
 	if (IS_ERR(rkey)) {
 		mutex_lock(&idmap->idmap_mutex);
 		rkey = request_key_with_auxdata(&key_type_id_resolver_legacy,
-						desc, "", 0, idmap);
+						desc, "", 0, idmap,
+						&nfs_idmap_key_acl);
 		mutex_unlock(&idmap->idmap_mutex);
 	}
-	if (!IS_ERR(rkey))
-		set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
 
 	kfree(desc);
 	return rkey;
@@ -310,8 +341,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen,
 	}
 
 	rcu_read_lock();
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto out_up;
diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..d2467ee1ccca 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -31,13 +31,12 @@
 /* key handle serial number */
 typedef int32_t key_serial_t;
 
-/* key handle permissions mask */
-typedef uint32_t key_perm_t;
-
 struct key;
 
 #ifdef CONFIG_KEYS
 
+#include <linux/keyctl.h>
+
 #undef KEY_DEBUGGING
 
 #define KEY_POS_VIEW	0x01000000	/* possessor can view a key's attributes */
@@ -72,8 +71,6 @@ struct key;
 #define KEY_OTH_SETATTR	0x00000020
 #define KEY_OTH_ALL	0x0000003f
 
-#define KEY_PERM_UNDEF	0xffffffff
-
 struct seq_file;
 struct user_struct;
 struct signal_struct;
@@ -95,6 +92,22 @@ union key_payload {
 	void			*data[4];
 };
 
+struct key_ace {
+	unsigned int		mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special_id;
+	};
+};
+
+struct key_acl {
+	refcount_t		usage;
+	unsigned int		nr_ace;
+	struct rcu_head		rcu;
+	struct key_ace		aces[];
+};
+
 /*****************************************************************************/
 /*
  * key reference with possession attribute handling
@@ -156,6 +169,7 @@ struct key {
 	struct rw_semaphore	sem;		/* change vs change sem */
 	struct key_user		*user;		/* owner of this key */
 	void			*security;	/* security data for this key */
+	struct key_acl		__rcu *acl;
 	union {
 		time_t		expiry;		/* time at which key expires (or 0) */
 		time_t		revoked_at;	/* time at which key was revoked */
@@ -163,7 +177,6 @@ struct key {
 	time_t			last_used_at;	/* last time used for LRU keyring discard */
 	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
 						 * - may not match RCU dereferenced payload
@@ -182,12 +195,11 @@ struct key {
 #define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
 #define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
 #define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
 #define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
 #define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
 #define KEY_FLAG_KEEP		10	/* set if key should not be removed */
 #define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_HAS_ACL	12	/* Set if KEYCTL_SETACL called on key */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -234,7 +246,7 @@ extern struct key *key_alloc(struct key_type *type,
 			     const char *desc,
 			     kuid_t uid, kgid_t gid,
 			     const struct cred *cred,
-			     key_perm_t perm,
+			     struct key_acl *acl,
 			     unsigned long flags,
 			     struct key_restriction *restrict_link);
 
@@ -268,24 +280,28 @@ static inline void key_ref_put(key_ref_t key_ref)
 
 extern struct key *request_key(struct key_type *type,
 			       const char *description,
-			       const char *callout_info);
+			       const char *callout_info,
+			       struct key_acl *acl);
 
 extern struct key *request_key_with_auxdata(struct key_type *type,
 					    const char *description,
 					    const void *callout_info,
 					    size_t callout_len,
-					    void *aux);
+					    void *aux,
+					    struct key_acl *acl);
 
 extern struct key *request_key_async(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
-				     size_t callout_len);
+				     size_t callout_len,
+				     struct key_acl *acl);
 
 extern struct key *request_key_async_with_auxdata(struct key_type *type,
 						  const char *description,
 						  const void *callout_info,
 						  size_t callout_len,
-						  void *aux);
+						  void *aux,
+						  struct key_acl *acl);
 
 extern int wait_for_key_construction(struct key *key, bool intr);
 
@@ -296,7 +312,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring,
 				      const char *description,
 				      const void *payload,
 				      size_t plen,
-				      key_perm_t perm,
+				      struct key_acl *acl,
 				      unsigned long flags);
 
 extern int key_update(key_ref_t key,
@@ -311,7 +327,7 @@ extern int key_unlink(struct key *keyring,
 
 extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 				 const struct cred *cred,
-				 key_perm_t perm,
+				 struct key_acl *acl,
 				 unsigned long flags,
 				 struct key_restriction *restrict_link,
 				 struct key *dest);
@@ -345,13 +361,16 @@ extern void key_set_timeout(struct key *, unsigned);
 /*
  * The permissions required on a key that we're looking up.
  */
-#define	KEY_NEED_VIEW	0x01	/* Require permission to view attributes */
-#define	KEY_NEED_READ	0x02	/* Require permission to read content */
-#define	KEY_NEED_WRITE	0x04	/* Require permission to update / modify */
-#define	KEY_NEED_SEARCH	0x08	/* Require permission to search (keyring) or find (key) */
-#define	KEY_NEED_LINK	0x10	/* Require permission to link */
-#define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
-#define	KEY_NEED_ALL	0x3f	/* All the above permissions */
+#define	KEY_NEED_VIEW	0x001	/* Require permission to view attributes */
+#define	KEY_NEED_READ	0x002	/* Require permission to read content */
+#define	KEY_NEED_WRITE	0x004	/* Require permission to update / modify */
+#define	KEY_NEED_SEARCH	0x008	/* Require permission to search (keyring) or find (key) */
+#define	KEY_NEED_LINK	0x010	/* Require permission to link */
+#define	KEY_NEED_INVAL	0x020	/* Require permission to invalidate key */
+#define	KEY_NEED_REVOKE	0x040	/* Require permission to revoke key */
+#define	KEY_NEED_SETSEC	0x080	/* Require permission to set owner, group, ACL */
+#define	KEY_NEED_JOIN	0x100	/* Require permission to join keyring as session */
+#define	KEY_NEED_CLEAR	0x200	/* Require permission to clear a keyring */
 
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
new file mode 100644
index 000000000000..7a738569a673
--- /dev/null
+++ b/include/linux/keyctl.h
@@ -0,0 +1,3 @@
+#define key_ace user_key_ace
+#include <uapi/linux/keyctl.h>
+#undef key_ace
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index ef16df06642a..8a9e829ef499 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -14,6 +14,40 @@
 
 #include <linux/types.h>
 
+/* Keyring ACL definitions */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
 /* special process keyring shortcut IDs */
 #define KEY_SPEC_THREAD_KEYRING		-1	/* - key ID for thread-specific keyring */
 #define KEY_SPEC_PROCESS_KEYRING	-2	/* - key ID for process-specific keyring */
@@ -61,6 +95,8 @@
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..9ef48527896a 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -45,6 +45,23 @@ const struct cred *dns_resolver_cache;
 
 #define	DNS_ERRORNO_OPTION	"dnserror"
 
+static struct key_acl dns_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_CLEAR,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /*
  * Preparse instantiation data for a dns_resolver key.
  *
@@ -279,8 +296,7 @@ static int __init init_dns_resolver(void)
 
 	keyring = keyring_alloc(".dns_resolver",
 				GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-				(KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				KEY_USR_VIEW | KEY_USR_READ,
+				&dns_keyring_acl,
 				KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -293,7 +309,6 @@ static int __init init_dns_resolver(void)
 
 	/* instruct request_key() to use this special keyring as a cache for
 	 * the results it looks up */
-	set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
 	cred->thread_keyring = keyring;
 	cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
 	dns_resolver_cache = cred;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index af781010753b..367186023af6 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -46,6 +46,23 @@
 
 #include "internal.h"
 
+static struct key_acl dns_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 3,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+	.aces[2] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_INVAL,
+		.special_id = KEY_ACE_NET_ADMIN,
+	},
+};
+
 /**
  * dns_query - Query the DNS
  * @type: Query type (or NULL for straight host->IP lookup)
@@ -122,7 +139,7 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	 * add_key() to preinstall malicious redirections
 	 */
 	saved_cred = override_creds(dns_resolver_cache);
-	rkey = request_key(&key_type_dns_resolver, desc, options);
+	rkey = request_key(&key_type_dns_resolver, desc, options, &dns_key_acl);
 	revert_creds(saved_cred);
 	kfree(desc);
 	if (IS_ERR(rkey)) {
@@ -131,9 +148,6 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	}
 
 	down_read(&rkey->sem);
-	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
-	rkey->perm |= KEY_USR_VIEW;
-
 	ret = key_validate(rkey);
 	if (ret < 0)
 		goto put;
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index e7f6b8823eb6..3e8639ac9f14 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -27,6 +27,15 @@
 #include <keys/user-type.h>
 #include "ar-internal.h"
 
+static struct key_acl rxrpc_null_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 1,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+};
+
 static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
@@ -912,7 +921,7 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen)
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_rxrpc, description, NULL);
+	key = request_key(&key_type_rxrpc, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -943,7 +952,7 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval,
 	if (IS_ERR(description))
 		return PTR_ERR(description);
 
-	key = request_key(&key_type_keyring, description, NULL);
+	key = request_key(&key_type_keyring, description, NULL, NULL);
 	if (IS_ERR(key)) {
 		kfree(description);
 		_leave(" = %ld", PTR_ERR(key));
@@ -1024,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname)
 
 	key = key_alloc(&key_type_rxrpc, keyname,
 			GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
-			KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			&rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(key))
 		return key;
 
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..dfcc68d341f5 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -303,7 +303,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 	const struct user_key_payload *upayload;
 	struct key *ukey;
 
-	ukey = request_key(&key_type_user, master_desc, NULL);
+	ukey = request_key(&key_type_user, master_desc, NULL, NULL);
 	if (IS_ERR(ukey))
 		goto error;
 
diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c
index cbf0bc127a73..675d335c93fd 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -34,7 +34,7 @@ struct key *request_trusted_key(const char *trusted_desc,
 	struct trusted_key_payload *tpayload;
 	struct key *tkey;
 
-	tkey = request_key(&key_type_trusted, trusted_desc, NULL);
+	tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL);
 	if (IS_ERR(tkey))
 		goto error;
 
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..094eca1e1f42 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,6 +155,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
+		key_put_acl(key->acl);
 
 		kfree(key->description);
 
@@ -225,7 +226,6 @@ static void key_garbage_collector(struct work_struct *work)
 			if (key->type == key_gc_dead_keytype) {
 				gc_state |= KEY_GC_FOUND_DEAD_KEY;
 				set_bit(KEY_FLAG_DEAD, &key->flags);
-				key->perm = 0;
 				goto skip_dead_key;
 			} else if (key->type == &key_type_keyring &&
 				   key->restrict_link) {
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 503adbae7b0d..a699c5937cbe 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,7 +88,8 @@ extern struct rb_root key_serial_tree;
 extern spinlock_t key_serial_lock;
 extern struct mutex key_construction_mutex;
 extern wait_queue_head_t request_key_conswq;
-
+extern struct key_acl default_key_acl;
+extern struct key_acl joinable_keyring_acl;
 
 extern struct key_type *key_type_lookup(const char *type);
 extern void key_type_put(struct key_type *ktype);
@@ -153,13 +154,14 @@ extern struct key *request_key_and_link(struct key_type *type,
 					const void *callout_info,
 					size_t callout_len,
 					void *aux,
+					struct key_acl *acl,
 					struct key *dest_keyring,
 					unsigned long flags);
 
 extern bool lookup_user_key_possessed(const struct key *key,
 				      const struct key_match_data *match_data);
 extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
-				 key_perm_t perm);
+				 u32 desired_perm);
 #define KEY_LOOKUP_CREATE	0x01
 #define KEY_LOOKUP_PARTIAL	0x02
 #define KEY_LOOKUP_FOR_UNLINK	0x04
@@ -178,7 +180,9 @@ extern void key_gc_keytype(struct key_type *ktype);
 
 extern int key_task_permission(const key_ref_t key_ref,
 			       const struct cred *cred,
-			       key_perm_t perm);
+			       u32 desired_perm);
+extern unsigned int key_acl_to_perm(const struct key_acl *acl);
+extern void key_put_acl(struct key_acl *acl);
 
 /*
  * Check to see whether permission is granted to use a key in the desired way.
@@ -234,7 +238,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
 				  const char __user *, key_serial_t);
 extern long keyctl_read_key(key_serial_t, char __user *, size_t);
 extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
-extern long keyctl_setperm_key(key_serial_t, key_perm_t);
+extern long keyctl_setperm_key(key_serial_t, unsigned int);
 extern long keyctl_instantiate_key(key_serial_t, const void __user *,
 				   size_t, key_serial_t);
 extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..f342bf586006 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -199,7 +199,7 @@ static inline void key_alloc_serial(struct key *key)
  * @uid: The owner of the new key.
  * @gid: The group ID for the new key's group permissions.
  * @cred: The credentials specifying UID namespace.
- * @perm: The permissions mask of the new key.
+ * @acl: The ACL to attach to the new key.
  * @flags: Flags specifying quota properties.
  * @restrict_link: Optional link restriction for new keyrings.
  *
@@ -227,7 +227,7 @@ static inline void key_alloc_serial(struct key *key)
  */
 struct key *key_alloc(struct key_type *type, const char *desc,
 		      kuid_t uid, kgid_t gid, const struct cred *cred,
-		      key_perm_t perm, unsigned long flags,
+		      struct key_acl *acl, unsigned long flags,
 		      struct key_restriction *restrict_link)
 {
 	struct key_user *user = NULL;
@@ -250,6 +250,9 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	desclen = strlen(desc);
 	quotalen = desclen + 1 + type->def_datalen;
 
+	if (!acl)
+		acl = &default_key_acl;
+
 	/* get hold of the key tracking for this user */
 	user = key_user_lookup(uid);
 	if (!user)
@@ -295,7 +298,8 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	key->datalen = type->def_datalen;
 	key->uid = uid;
 	key->gid = gid;
-	key->perm = perm;
+	refcount_inc(&acl->usage);
+	rcu_assign_pointer(key->acl, acl);
 	key->restrict_link = restrict_link;
 
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))
@@ -775,7 +779,7 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
  * @description: The searchable description for the key.
  * @payload: The data to use to instantiate or update the key.
  * @plen: The length of @payload.
- * @perm: The permissions mask for a new key.
+ * @acl: The ACL to attach if a key is created.
  * @flags: The quota flags for a new key.
  *
  * Search the destination keyring for a key of the same description and if one
@@ -798,7 +802,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			       const char *description,
 			       const void *payload,
 			       size_t plen,
-			       key_perm_t perm,
+			       struct key_acl *acl,
 			       unsigned long flags)
 {
 	struct keyring_index_key index_key = {
@@ -889,22 +893,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 			goto found_matching_key;
 	}
 
-	/* if the client doesn't provide, decide on the permissions we want */
-	if (perm == KEY_PERM_UNDEF) {
-		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-		perm |= KEY_USR_VIEW;
-
-		if (index_key.type->read)
-			perm |= KEY_POS_READ;
-
-		if (index_key.type == &key_type_keyring ||
-		    index_key.type->update)
-			perm |= KEY_POS_WRITE;
-	}
-
 	/* allocate a new key */
 	key = key_alloc(index_key.type, index_key.description,
-			cred->fsuid, cred->fsgid, cred, perm, flags, NULL);
+			cred->fsuid, cred->fsgid, cred, acl, flags, NULL);
 	if (IS_ERR(key)) {
 		key_ref = ERR_CAST(key);
 		goto error_link_end;
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..51fefec80cce 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -120,8 +120,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type,
 	/* create or update the requested key and add it to the target
 	 * keyring */
 	key_ref = key_create_or_update(keyring_ref, type, description,
-				       payload, plen, KEY_PERM_UNDEF,
-				       KEY_ALLOC_IN_QUOTA);
+				       payload, plen, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key_ref)) {
 		ret = key_ref_to_ptr(key_ref)->serial;
 		key_ref_put(key_ref);
@@ -211,7 +210,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
 
 	/* do the search */
 	key = request_key_and_link(ktype, description, callout_info,
-				   callout_len, NULL, key_ref_to_ptr(dest_ref),
+				   callout_len, NULL, NULL,
+				   key_ref_to_ptr(dest_ref),
 				   KEY_ALLOC_IN_QUOTA);
 	if (IS_ERR(key)) {
 		ret = PTR_ERR(key);
@@ -373,16 +373,10 @@ long keyctl_revoke_key(key_serial_t id)
 	struct key *key;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-		if (ret != -EACCES)
-			goto error;
-		key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
-		if (IS_ERR(key_ref)) {
-			ret = PTR_ERR(key_ref);
-			goto error;
-		}
+		goto error;
 	}
 
 	key = key_ref_to_ptr(key_ref);
@@ -416,32 +410,18 @@ long keyctl_invalidate_key(key_serial_t id)
 
 	kenter("%d", id);
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
-
-		/* Root is permitted to invalidate certain special keys */
-		if (capable(CAP_SYS_ADMIN)) {
-			key_ref = lookup_user_key(id, 0, 0);
-			if (IS_ERR(key_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_INVAL,
-				     &key_ref_to_ptr(key_ref)->flags))
-				goto invalidate;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-invalidate:
 	key = key_ref_to_ptr(key_ref);
 	ret = 0;
 	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 		ret = -EPERM;
 	else
 		key_invalidate(key);
-error_put:
 	key_ref_put(key_ref);
 error:
 	kleave(" = %ld", ret);
@@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
 	struct key *keyring;
 	long ret;
 
-	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
+	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
 	if (IS_ERR(keyring_ref)) {
 		ret = PTR_ERR(keyring_ref);
-
-		/* Root is permitted to invalidate certain special keyrings */
-		if (capable(CAP_SYS_ADMIN)) {
-			keyring_ref = lookup_user_key(ringid, 0, 0);
-			if (IS_ERR(keyring_ref))
-				goto error;
-			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
-				     &key_ref_to_ptr(keyring_ref)->flags))
-				goto clear;
-			goto error_put;
-		}
-
 		goto error;
 	}
 
-clear:
 	keyring = key_ref_to_ptr(keyring_ref);
 	if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 		ret = -EPERM;
 	else
 		ret = keyring_clear(keyring);
-error_put:
 	key_ref_put(keyring_ref);
 error:
 	return ret;
@@ -590,6 +556,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			 size_t buflen)
 {
 	struct key *key, *instkey;
+	unsigned int perm;
 	key_ref_t key_ref;
 	char *infobuf;
 	long ret;
@@ -619,6 +586,10 @@ long keyctl_describe_key(key_serial_t keyid,
 	key = key_ref_to_ptr(key_ref);
 	desclen = strlen(key->description);
 
+	rcu_read_lock();
+	perm = key_acl_to_perm(rcu_dereference(key->acl));
+	rcu_read_unlock();
+
 	/* calculate how much information we're going to return */
 	ret = -ENOMEM;
 	infobuf = kasprintf(GFP_KERNEL,
@@ -626,7 +597,7 @@ long keyctl_describe_key(key_serial_t keyid,
 			    key->type->name,
 			    from_kuid_munged(current_user_ns(), key->uid),
 			    from_kgid_munged(current_user_ns(), key->gid),
-			    key->perm);
+			    perm);
 	if (!infobuf)
 		goto error2;
 	infolen = strlen(infobuf);
@@ -844,7 +815,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		goto error;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -939,18 +910,25 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
  * the key need not be fully instantiated yet.  If the caller does not have
  * sysadmin capability, it may only change the permission on keys that it owns.
  */
-long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
+long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 {
+	struct key_acl *acl, *discard;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
+	int nr, i, j;
 
-	ret = -EINVAL;
 	if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
-		goto error;
+		return -EINVAL;
+	
+	nr = 0;
+	if (perm & KEY_POS_ALL) nr++;
+	if (perm & KEY_USR_ALL) nr++;
+	if (perm & KEY_GRP_ALL) nr++;
+	if (perm & KEY_OTH_ALL) nr++;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref)) {
 		ret = PTR_ERR(key_ref);
 		goto error;
@@ -958,17 +936,55 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 
 	key = key_ref_to_ptr(key_ref);
 
+	ret = -EOPNOTSUPP;
+	if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+		goto error_key;
+
+	ret = -ENOMEM;
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr,
+		      GFP_KERNEL);
+	if (!acl)
+		goto error_key;
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr;
+	j = 0;
+	for (i = 0; i < 4; i++) {
+		struct key_ace *ace = &acl->aces[j];
+		unsigned int subset = (perm >> (i * 8)) & 0x3f;
+
+		if (!subset)
+			continue;
+		ace->special_id = KEY_ACE_EVERYONE + i;
+		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);
+		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+			ace->mask |= KEY_ACE_REVOKE;
+		if (subset & KEY_OTH_SETATTR)
+			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (key->type == &key_type_keyring) {
+			ace->mask |= KEY_ACE_JOIN;
+			if (subset & KEY_OTH_WRITE)
+				ace->mask |= KEY_ACE_CLEAR;
+		}
+		j++;
+	}
+
 	/* make the changes with the locks held to prevent chown/chmod races */
+	discard = acl;
 	ret = -EACCES;
 	down_write(&key->sem);
 
 	/* if we're not the sysadmin, we can only change a key that we own */
 	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
-		key->perm = perm;
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
 		ret = 0;
 	}
 
 	up_write(&key->sem);
+	key_put_acl(discard);
+error_key:
 	key_put(key);
 error:
 	return ret;
@@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 	long ret;
 
 	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
-				  KEY_NEED_SETATTR);
+				  KEY_NEED_WRITE);
 	if (IS_ERR(key_ref)) {
 		/* setting the timeout on a key under construction is permitted
 		 * if we have the authorisation token handy */
@@ -1604,7 +1620,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 	char *restriction = NULL;
 	long ret;
 
-	key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR);
+	key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC);
 	if (IS_ERR(key_ref))
 		return PTR_ERR(key_ref);
 
@@ -1692,7 +1708,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 
 	case KEYCTL_SETPERM:
 		return keyctl_setperm_key((key_serial_t) arg2,
-					  (key_perm_t) arg3);
+					  (unsigned int)arg3);
 
 	case KEYCTL_INSTANTIATE:
 		return keyctl_instantiate_key((key_serial_t) arg2,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..ea7e2620ca03 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -489,11 +489,19 @@ static long keyring_read(const struct key *keyring,
 	return ctx.count;
 }
 
-/*
- * Allocate a keyring and link into the destination keyring.
+/**
+ * keyring_alloc - Allocate a keyring and link into the destination
+ * @description: The key description to allow the key to be searched out.
+ * @uid: The owner of the new key.
+ * @gid: The group ID for the new key's group permissions.
+ * @cred: The credentials specifying UID namespace.
+ * @acl: The ACL to attach to the new key.
+ * @flags: Flags specifying quota properties.
+ * @restrict_link: Optional link restriction for new keyrings.
+ * @dest: Destination keyring.
  */
 struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
-			  const struct cred *cred, key_perm_t perm,
+			  const struct cred *cred, struct key_acl *acl,
 			  unsigned long flags,
 			  struct key_restriction *restrict_link,
 			  struct key *dest)
@@ -502,7 +510,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid,
 	int ret;
 
 	keyring = key_alloc(&key_type_keyring, description,
-			    uid, gid, cred, perm, flags, restrict_link);
+			    uid, gid, cred, acl, flags, restrict_link);
 	if (!IS_ERR(keyring)) {
 		ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL);
 		if (ret < 0) {
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..5e186b0f802d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,41 @@
 
 #include <linux/module.h>
 #include <linux/security.h>
+#include <linux/user_namespace.h>
 #include "internal.h"
 
+struct key_acl default_key_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+struct key_acl joinable_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_READ |
+					   KEY_ACE_LINK | KEY_ACE_JOIN),
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /**
  * key_task_permission - Check a key can be used
  * @key_ref: The key to check.
  * @cred: The credentials to use.
- * @perm: The permissions to check for.
+ * @desired_perm: The permissions to check for.
  *
  * Check to see whether permission is granted to use a key in the desired way,
  * but permit the security modules to override.
@@ -28,53 +56,83 @@
  * permissions bits or the LSM check.
  */
 int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
-			unsigned perm)
+			unsigned int desired_perm)
 {
-	struct key *key;
-	key_perm_t kperm;
-	int ret;
+	const struct key_acl *acl;
+	const struct key *key;
+	unsigned int allow = 0;
+	int i;
 
 	key = key_ref_to_ptr(key_ref);
 
-	/* use the second 8-bits of permissions for keys the caller owns */
-	if (uid_eq(key->uid, cred->fsuid)) {
-		kperm = key->perm >> 16;
-		goto use_these_perms;
-	}
-
-	/* use the third 8-bits of permissions for keys the caller has a group
-	 * membership in common with */
-	if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
-		if (gid_eq(key->gid, cred->fsgid)) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
-		}
-
-		ret = groups_search(cred->group_info, key->gid);
-		if (ret) {
-			kperm = key->perm >> 8;
-			goto use_these_perms;
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	if (!acl || acl->nr_ace == 0)
+		goto no_access_rcu;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				if (is_key_possessed(key_ref))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_OWNER:
+				if (uid_eq(key->uid, cred->fsuid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_GROUP:
+				if (gid_valid(key->gid) &&
+				    gid_eq(key->gid, cred->fsgid))
+					allow |= ace->mask;
+				else if (groups_search(cred->group_info, key->gid))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_EVERYONE:
+				allow |= ace->mask;
+				break;
+			case KEY_ACE_ROOT:
+				if (uid_eq(key->uid, cred->user_ns->owner))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_SYS_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
+					allow |= ace->mask;
+				break;
+			case KEY_ACE_NET_ADMIN:
+				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
+					allow |= ace->mask;
+				break;
+			}
+			break;
+		case KEY_ACE_UID:
+			if (uid_eq(ace->uid, cred->fsuid))
+				allow |= ace->mask;
+			break;
+		case KEY_ACE_GID:
+			if (gid_eq(ace->gid, cred->fsgid))
+				allow |= ace->mask;
+			else if (groups_search(cred->group_info, ace->gid))
+				allow |= ace->mask;
+			break;
 		}
 	}
 
-	/* otherwise use the least-significant 8-bits */
-	kperm = key->perm;
-
-use_these_perms:
+	rcu_read_unlock();
 
-	/* use the top 8-bits of permissions for keys the caller possesses
-	 * - possessor permissions are additive with other permissions
-	 */
-	if (is_key_possessed(key_ref))
-		kperm |= key->perm >> 24;
+	if (!(allow & desired_perm))
+		goto no_access;
 
-	kperm = kperm & perm & KEY_NEED_ALL;
+	return security_key_permission(key_ref, cred, desired_perm);
 
-	if (kperm != perm)
-		return -EACCES;
-
-	/* let LSM be the final arbiter */
-	return security_key_permission(key_ref, cred, perm);
+no_access_rcu:
+	rcu_read_unlock();
+no_access:
+	return -EACCES;
 }
 EXPORT_SYMBOL(key_task_permission);
 
@@ -108,3 +166,80 @@ int key_validate(const struct key *key)
 	return 0;
 }
 EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to a permissions mask.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+	unsigned int perm = 0;
+	int i;
+
+	if (!acl || acl->nr_ace == 0)
+		return 0;
+
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		unsigned int mask = ace->mask & KEY_ACE__PERMS;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			switch (ace->special_id) {
+			case KEY_ACE_POSSESSOR:
+				perm |= (mask & 0x1f) << 24;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_POS_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_POS_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
+					perm |= KEY_POS_WRITE;
+				break;
+			case KEY_ACE_OWNER:
+				perm |= (mask & 0x1f) << 16;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_USR_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_USR_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
+					perm |= KEY_USR_WRITE;
+				break;
+			case KEY_ACE_GROUP:
+				perm |= (mask & 0x1f) << 8;
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_GRP_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_GRP_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
+					perm |= KEY_GRP_WRITE;
+				break;
+			case KEY_ACE_EVERYONE:
+				perm |= (mask & 0x1f);
+				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
+					perm |= KEY_OTH_SETATTR;
+				if (mask & KEY_ACE_CLEAR)
+					perm |= KEY_OTH_WRITE;
+				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
+					perm |= KEY_OTH_WRITE;
+				break;
+			}
+		}
+
+		//printk("[%d] %08x %08x %08x\n", i, ace->special_id, ace->mask, perm);
+	}
+
+	return perm;
+}
+
+static void key_acl_rcu(struct rcu_head *rcu)
+{
+	kfree(container_of(rcu, struct key_acl, rcu));
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+	if (refcount_dec_and_test(&acl->usage))
+		call_rcu(&acl->rcu, key_acl_rcu);
+}
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index d0cb5b32eff7..0410a196a8f7 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -16,6 +16,33 @@
 
 unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
 
+static struct key_acl persistent_register_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_SEARCH | KEY_ACE_WRITE,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl persistent_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | (KEY_ACE__ORDINARY | KEY_ACE_CLEAR |
+					   KEY_ACE_INVAL),
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Create the persistent keyring register for the current user namespace.
  *
@@ -26,8 +53,7 @@ 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),
+					&persistent_register_keyring_acl,
 					KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
 	if (IS_ERR(reg))
 		return PTR_ERR(reg);
@@ -60,8 +86,7 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid,
 
 	persistent = keyring_alloc(index_key->description,
 				   uid, INVALID_GID, current_cred(),
-				   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
-				    KEY_USR_VIEW | KEY_USR_READ),
+				   &persistent_keyring_acl,
 				   KEY_ALLOC_NOT_IN_QUOTA, NULL,
 				   ns->persistent_keyring_register);
 	if (IS_ERR(persistent))
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4361455abe78 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -176,13 +176,15 @@ static void proc_keys_stop(struct seq_file *p, void *v)
 
 static int proc_keys_show(struct seq_file *m, void *v)
 {
+	const struct key_acl *acl;
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
-	int rc;
+	bool check_pos;
+	int i, rc;
 
 	struct keyring_search_context ctx = {
 		.index_key.type		= key->type,
@@ -194,12 +196,25 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		.flags			= KEYRING_SEARCH_NO_STATE_CHECK,
 	};
 
-	key_ref = make_key_ref(key, 0);
+	rcu_read_lock();
+
+	acl = rcu_dereference(key->acl);
+	check_pos = false;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+		if (ace->special_id == KEY_ACE_POSSESSOR) {
+			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
+			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
+				check_pos = true;
+			break;
+		}
+	}
 
 	/* determine if the key is possessed by this process (a test we can
 	 * skip if the key does not indicate the possessor can view it
 	 */
-	if (key->perm & KEY_POS_VIEW) {
+	key_ref = make_key_ref(key, 0);
+	if (check_pos) {
 		skey_ref = search_my_process_keyrings(&ctx);
 		if (!IS_ERR(skey_ref)) {
 			key_ref_put(skey_ref);
@@ -214,8 +229,6 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	now = current_kernel_time();
 
-	rcu_read_lock();
-
 	/* come up with a suitable timeout value */
 	if (key->expiry == 0) {
 		memcpy(xbuf, "perm", 5);
@@ -250,7 +263,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
-		   key->perm,
+		   key_acl_to_perm(acl),
 		   from_kuid_munged(seq_user_ns(m), key->uid),
 		   from_kgid_munged(seq_user_ns(m), key->gid),
 		   key->type->name);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..29c08dca78e6 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -38,6 +38,45 @@ struct key_user root_key_user = {
 	.uid		= GLOBAL_ROOT_UID,
 };
 
+static struct key_acl user_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__ORDINARY,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl tp_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
+static struct key_acl session_keyring_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * Install the user and user session keyrings for the current process's UID.
  */
@@ -46,12 +85,10 @@ int install_user_keyrings(void)
 	struct user_struct *user;
 	const struct cred *cred;
 	struct key *uid_keyring, *session_keyring;
-	key_perm_t user_keyring_perm;
 	char buf[20];
 	int ret;
 	uid_t uid;
 
-	user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
 	cred = current_cred();
 	user = cred->user;
 	uid = from_kuid(cred->user_ns, user->uid);
@@ -76,9 +113,9 @@ int install_user_keyrings(void)
 		uid_keyring = find_keyring_by_name(buf, true);
 		if (IS_ERR(uid_keyring)) {
 			uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
-						    cred, user_keyring_perm,
+						    cred, &user_keyring_acl,
 						    KEY_ALLOC_UID_KEYRING |
-							KEY_ALLOC_IN_QUOTA,
+						    KEY_ALLOC_IN_QUOTA,
 						    NULL, NULL);
 			if (IS_ERR(uid_keyring)) {
 				ret = PTR_ERR(uid_keyring);
@@ -94,9 +131,9 @@ int install_user_keyrings(void)
 		if (IS_ERR(session_keyring)) {
 			session_keyring =
 				keyring_alloc(buf, user->uid, INVALID_GID,
-					      cred, user_keyring_perm,
+					      cred, &user_keyring_acl,
 					      KEY_ALLOC_UID_KEYRING |
-						  KEY_ALLOC_IN_QUOTA,
+					      KEY_ALLOC_IN_QUOTA,
 					      NULL, NULL);
 			if (IS_ERR(session_keyring)) {
 				ret = PTR_ERR(session_keyring);
@@ -143,8 +180,7 @@ int install_thread_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -190,8 +226,7 @@ int install_process_keyring_to_cred(struct cred *new)
 		return 0;
 
 	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
-				KEY_POS_ALL | KEY_USR_VIEW,
-				KEY_ALLOC_QUOTA_OVERRUN,
+				&tp_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN,
 				NULL, NULL);
 	if (IS_ERR(keyring))
 		return PTR_ERR(keyring);
@@ -244,8 +279,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
 			flags = KEY_ALLOC_IN_QUOTA;
 
 		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
-					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-					flags, NULL, NULL);
+					&session_keyring_acl, flags, NULL, NULL);
 		if (IS_ERR(keyring))
 			return PTR_ERR(keyring);
 	} else {
@@ -531,7 +565,7 @@ bool lookup_user_key_possessed(const struct key *key,
  * returned key reference.
  */
 key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
-			  key_perm_t perm)
+			  unsigned int desired_perm)
 {
 	struct keyring_search_context ctx = {
 		.match_data.cmp		= lookup_user_key_possessed,
@@ -717,12 +751,12 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		case -ERESTARTSYS:
 			goto invalid_key;
 		default:
-			if (perm)
+			if (desired_perm)
 				goto invalid_key;
 		case 0:
 			break;
 		}
-	} else if (perm) {
+	} else if (desired_perm) {
 		ret = key_validate(key);
 		if (ret < 0)
 			goto invalid_key;
@@ -734,9 +768,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 		goto invalid_key;
 
 	/* check the permissions */
-	ret = key_task_permission(key_ref, ctx.cred, perm);
-	if (ret < 0)
-		goto invalid_key;
+	if (desired_perm) {
+		ret = key_task_permission(key_ref, ctx.cred, desired_perm);
+		if (ret < 0)
+			goto invalid_key;
+	}
 
 	key->last_used_at = current_kernel_time().tv_sec;
 
@@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
 	if (PTR_ERR(keyring) == -ENOKEY) {
 		/* not found - try and create a new one */
 		keyring = keyring_alloc(
-			name, old->uid, old->gid, old,
-			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
+			name, old->uid, old->gid, old, &joinable_keyring_acl,
 			KEY_ALLOC_IN_QUOTA, NULL, NULL);
 		if (IS_ERR(keyring)) {
 			ret = PTR_ERR(keyring);
@@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
 		goto error3;
 	}
 
+	ret = key_task_permission(make_key_ref(keyring, false), old,
+				  KEY_NEED_JOIN);
+	if (ret < 0)
+		goto error3;
+
 	/* we've got a keyring - now to install it */
 	ret = install_session_keyring_to_cred(new, keyring);
 	if (ret < 0)
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..2f7906c231a6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -116,8 +116,7 @@ static int call_sbin_request_key(struct key_construction *cons,
 
 	cred = get_current_cred();
 	keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
-				KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
-				KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
+				NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL);
 	put_cred(cred);
 	if (IS_ERR(keyring)) {
 		ret = PTR_ERR(keyring);
@@ -332,11 +331,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 			       struct key *dest_keyring,
 			       unsigned long flags,
 			       struct key_user *user,
+			       struct key_acl *acl,
 			       struct key **_key)
 {
 	struct assoc_array_edit *edit;
 	struct key *key;
-	key_perm_t perm;
 	key_ref_t key_ref;
 	int ret;
 
@@ -346,17 +345,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 	*_key = NULL;
 	mutex_lock(&user->cons_lock);
 
-	perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
-	perm |= KEY_USR_VIEW;
-	if (ctx->index_key.type->read)
-		perm |= KEY_POS_READ;
-	if (ctx->index_key.type == &key_type_keyring ||
-	    ctx->index_key.type->update)
-		perm |= KEY_POS_WRITE;
-
 	key = key_alloc(ctx->index_key.type, ctx->index_key.description,
 			ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
-			perm, flags, NULL);
+			NULL, flags, NULL);
 	if (IS_ERR(key))
 		goto alloc_failed;
 
@@ -432,6 +423,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 					  const char *callout_info,
 					  size_t callout_len,
 					  void *aux,
+					  struct key_acl *acl,
 					  struct key *dest_keyring,
 					  unsigned long flags)
 {
@@ -450,7 +442,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
 
 	construct_get_dest_keyring(&dest_keyring);
 
-	ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
+	ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key);
 	key_user_put(user);
 
 	if (ret == 0) {
@@ -486,6 +478,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  * @dest_keyring: Where to cache the key.
  * @flags: Flags to key_alloc().
  *
@@ -511,6 +504,7 @@ struct key *request_key_and_link(struct key_type *type,
 				 const void *callout_info,
 				 size_t callout_len,
 				 void *aux,
+				 struct key_acl *acl,
 				 struct key *dest_keyring,
 				 unsigned long flags)
 {
@@ -565,7 +559,7 @@ struct key *request_key_and_link(struct key_type *type,
 			goto error_free;
 
 		key = construct_key_and_link(&ctx, callout_info, callout_len,
-					     aux, dest_keyring, flags);
+					     aux, acl, dest_keyring, flags);
 	}
 
 error_free:
@@ -608,6 +602,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
  * @type: Type of key.
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota,
@@ -619,7 +614,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
  */
 struct key *request_key(struct key_type *type,
 			const char *description,
-			const char *callout_info)
+			const char *callout_info,
+			struct key_acl *acl)
 {
 	struct key *key;
 	size_t callout_len = 0;
@@ -628,7 +624,7 @@ struct key *request_key(struct key_type *type,
 	if (callout_info)
 		callout_len = strlen(callout_info);
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   NULL, NULL, KEY_ALLOC_IN_QUOTA);
+				   NULL, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -647,6 +643,7 @@ EXPORT_SYMBOL(request_key);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -658,13 +655,14 @@ struct key *request_key_with_auxdata(struct key_type *type,
 				     const char *description,
 				     const void *callout_info,
 				     size_t callout_len,
-				     void *aux)
+				     void *aux,
+				     struct key_acl *acl)
 {
 	struct key *key;
 	int ret;
 
 	key = request_key_and_link(type, description, callout_info, callout_len,
-				   aux, NULL, KEY_ALLOC_IN_QUOTA);
+				   aux, acl, NULL, KEY_ALLOC_IN_QUOTA);
 	if (!IS_ERR(key)) {
 		ret = wait_for_key_construction(key, false);
 		if (ret < 0) {
@@ -682,6 +680,7 @@ EXPORT_SYMBOL(request_key_with_auxdata);
  * @description: The searchable description of the key.
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found, new keys are always allocated in the user's quota and
@@ -693,10 +692,11 @@ EXPORT_SYMBOL(request_key_with_auxdata);
 struct key *request_key_async(struct key_type *type,
 			      const char *description,
 			      const void *callout_info,
-			      size_t callout_len)
+			      size_t callout_len,
+			      struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, NULL, NULL,
+				    callout_len, NULL, acl, NULL,
 				    KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async);
@@ -708,6 +708,7 @@ EXPORT_SYMBOL(request_key_async);
  * @callout_info: The data to pass to the instantiation upcall (or NULL).
  * @callout_len: The length of callout_info.
  * @aux: Auxiliary data for the upcall.
+ * @acl: The ACL to attach if a new key is created.
  *
  * As for request_key_and_link() except that it does not add the returned key
  * to a keyring if found and new keys are always allocated in the user's quota.
@@ -719,9 +720,11 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
 					   const char *description,
 					   const void *callout_info,
 					   size_t callout_len,
-					   void *aux)
+					   void *aux,
+					   struct key_acl *acl)
 {
 	return request_key_and_link(type, description, callout_info,
-				    callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
+				    callout_len, aux, acl,
+				    NULL, KEY_ALLOC_IN_QUOTA);
 }
 EXPORT_SYMBOL(request_key_async_with_auxdata);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..d810a54070f7 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -29,6 +29,19 @@ static void request_key_auth_revoke(struct key *);
 static void request_key_auth_destroy(struct key *);
 static long request_key_auth_read(const struct key *, char __user *, size_t);
 
+static struct key_acl request_key_auth_acl = {
+	.usage	= REFCOUNT_INIT(1),
+	.nr_ace	= 2,
+	.aces[0] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH,
+		.special_id = KEY_ACE_POSSESSOR,
+	},
+	.aces[1] = {
+		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
+		.special_id = KEY_ACE_OWNER,
+	},
+};
+
 /*
  * The request-key authorisation key type definition.
  */
@@ -203,8 +216,8 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
 
 	authkey = key_alloc(&key_type_request_key_auth, desc,
 			    cred->fsuid, cred->fsgid, cred,
-			    KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
-			    KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA, NULL);
+			    &request_key_auth_acl,
+			    KEY_ALLOC_NOT_IN_QUOTA, NULL);
 	if (IS_ERR(authkey)) {
 		ret = PTR_ERR(authkey);
 		goto error_free_rka;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f5d304736852..7c06837802be 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
+	unsigned oldstyle_perm;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
 	if (perm == 0)
 		return 0;
 
+	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
+				KEY_NEED_SEARCH | KEY_NEED_LINK);
+	if (perm & KEY_NEED_INVAL)
+		oldstyle_perm |= KEY_NEED_SEARCH;
+	if (perm & KEY_NEED_REVOKE)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	if (perm & KEY_NEED_SETSEC)
+		oldstyle_perm |= 0x20;
+	if (perm & KEY_NEED_JOIN)
+		oldstyle_perm |= KEY_NEED_LINK;
+	if (perm & KEY_NEED_CLEAR)
+		oldstyle_perm |= KEY_NEED_WRITE;
+	
 	sid = cred_sid(cred);
 
 	key = key_ref_to_ptr(key_ref);
 	ksec = key->security;
 
-	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+	return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 319add31b4a4..2cd323206310 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
 #endif
 	if (perm & KEY_NEED_READ)
 		request = MAY_READ;
-	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
+	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
+		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
 		request = MAY_WRITE;
 	rc = smk_access(tkp, keyp->security, request, &ad);
 	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] keyutils: Add key/keyring ACL support
  2017-09-26 23:05 ` David Howells
  (?)
@ 2017-09-27 16:10   ` David Howells
  -1 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 16:10 UTC (permalink / raw)
  To: linux-security-module

Here's a patch to make keyutils support ACLs on keys.

[root@andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl setacl $k all:all u.4:r pos:0x37f root:I net:c
[root@andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl getacl $k
IDENTITY  ID         PERMS
===== ===== =========all       -          0fffffff cjSRIlswrv
uid                4 00000002 --------r-
possessor -          0000037f cj-RIlswrv
root      -          00000020 ----I-----
net       -          00000200 c---------


David
---
diff --git a/keyctl.c b/keyctl.c
index 61990b4..c8b6f6f 100644
--- a/keyctl.c
+++ b/keyctl.c
@@ -71,6 +71,9 @@ static nr void act_keyctl_dh_compute(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf_oi(int argc, char *argv[]);
 static nr void act_keyctl_restrict_keyring(int argc, char *argv[]);
+static nr void act_keyctl_getacl(int argc, char *argv[]);
+static nr void act_keyctl_rgetacl(int argc, char *argv[]);
+static nr void act_keyctl_setacl(int argc, char *argv[]);
 
 const struct command commands[] = {
 	{ act_keyctl___version,	"--version",	"" },
@@ -82,9 +85,10 @@ const struct command commands[] = {
 	{ act_keyctl_dh_compute, "dh_compute",	"<private> <prime> <base>" },
 	{ act_keyctl_dh_compute_kdf, "dh_compute_kdf", "<private> <prime> <base> <len> <hash_name>" },
 	{ act_keyctl_dh_compute_kdf_oi, "dh_compute_kdf_oi", "<private> <prime> <base> <len> <hash_name>" },
+	{ act_keyctl_getacl,	"getacl",	"<key>" },
+	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_instantiate, "instantiate","<key> <data> <keyring>" },
 	{ act_keyctl_invalidate,"invalidate",	"<key>" },
-	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_link,	"link",		"<key> <keyring>" },
 	{ act_keyctl_list,	"list",		"<keyring>" },
 	{ act_keyctl_negate,	"negate",	"<key> <timeout> <keyring>" },
@@ -107,12 +111,14 @@ const struct command commands[] = {
 	{ act_keyctl_request2,	"request2",	"<type> <desc> <info> [<dest_keyring>]" },
 	{ act_keyctl_restrict_keyring, "restrict_keyring", "<keyring> [<type> [<restriction>]]" },
 	{ act_keyctl_revoke,	"revoke",	"<key>" },
+	{ act_keyctl_rgetacl,	"rgetacl",	"<key>" },
 	{ act_keyctl_rlist,	"rlist",	"<keyring>" },
 	{ act_keyctl_search,	"search",	"<keyring> <type> <desc> [<dest_keyring>]" },
 	{ act_keyctl_security,	"security",	"<key>" },
 	{ act_keyctl_session,	"session",	"" },
 	{ NULL,			"session",	"- [<prog> <arg1> <arg2> ...]" },
 	{ NULL,			"session",	"<name> [<prog> <arg1> <arg2> ...]" },
+	{ act_keyctl_setacl,	"setacl",	"<key> [<ace> <ace> ...]" },
 	{ act_keyctl_setperm,	"setperm",	"<key> <mask>" },
 	{ act_keyctl_show,	"show",		"[-x] [<keyring>]" },
 	{ act_keyctl_timeout,	"timeout",	"<key> <timeout>" },
@@ -1841,6 +1847,226 @@ static void act_keyctl_restrict_keyring(int argc, char *argv[])
 	exit(0);
 }
 
+static const char *special_ace_ids[] = {
+	[0]			= "?0",
+	[KEY_ACE_EVERYONE]	= "all",
+	[KEY_ACE_GROUP]		= "group",
+	[KEY_ACE_OWNER]		= "owner",
+	[KEY_ACE_POSSESSOR]	= "possessor",
+	[KEY_ACE_ROOT]		= "root",
+	[KEY_ACE_SYS_ADMIN]	= "sys",
+	[KEY_ACE_NET_ADMIN]	= "net",
+};
+
+/*
+ * Display ACL.
+ */
+static void display_acl(const struct key_ace *acl, int nr_ace)
+{
+	int i;
+
+	printf("IDENTITY  ID         PERMS\n");
+	printf("===== ===== ==========\n");
+
+	for (i = 0; i < nr_ace; i++) {
+		const struct key_ace *ace = &acl[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (ace->special_id <= KEY_ACE_NET_ADMIN)
+				printf("%-9.9s -         ", special_ace_ids[ace->special_id]);
+			else
+				printf("?special  %10u", ace->special_id);
+			break;
+		case KEY_ACE_UID:
+			printf("uid       %10u", ace->uid);
+			break;
+		case KEY_ACE_GID:
+			printf("gid       %10u", ace->gid);
+			break;
+		default:
+			printf("?%x        %10u",
+			       (ace->mask & KEY_ACE__IDENTITY) >> 28,
+			       ace->special_id);
+			break;
+		}
+
+		printf(" %08x ", ace->mask & KEY_ACE__PERMS);
+		printf("%c%c%c%c%c%c%c%c%c%c\n",
+		       ace->mask & KEY_ACE_CLEAR	? 'c' : '-',
+		       ace->mask & KEY_ACE_JOIN		? 'j' : '-',
+		       ace->mask & KEY_ACE_SET_SECURITY	? 'S' : '-',
+		       ace->mask & KEY_ACE_REVOKE	? 'R' : '-',
+		       ace->mask & KEY_ACE_INVAL	? 'I' : '-',
+		       ace->mask & KEY_ACE_LINK		? 'l' : '-',
+		       ace->mask & KEY_ACE_SEARCH	? 's' : '-',
+		       ace->mask & KEY_ACE_WRITE	? 'w' : '-',
+		       ace->mask & KEY_ACE_READ		? 'r' : '-',
+		       ace->mask & KEY_ACE_VIEW		? 'v' : '-');
+	}
+}
+
+/*
+ * Get a key's ACL.
+ */
+static void act_keyctl_getacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	display_acl(acl, nr_ace);
+	exit(0);
+}
+
+/*
+ * Get a key's ACL in raw hex form.
+ */
+static void act_keyctl_rgetacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace, i;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	for (i = 0; i < nr_ace; i++) {
+		if (i != 0)
+			putchar(';');
+		printf("%x:%x", acl[i].mask, acl[i].special_id);
+	}
+
+	putchar('\n');
+	exit(0);
+}
+
+/*
+ * Set a key's ACL.
+ */
+static void act_keyctl_setacl(int argc, char *argv[])
+{
+	struct key_ace *acl = NULL, *ace;
+	key_serial_t key;
+	unsigned ltmp;
+	unsigned tmp, len;
+	char *id, *colon, *perm, *p;
+	int nr_ace, i;
+
+	if (argc < 2)
+		format();
+
+	key = get_key_id(argv[1]);
+	nr_ace = argc - 2;
+
+	if (nr_ace > 0) {
+		acl = alloca(sizeof(*acl) * nr_ace);
+		memset(acl, 0, sizeof(*acl) * nr_ace);
+
+		for (i = 0; i < nr_ace; i++) {
+			ace = &acl[i];
+			id = argv[i + 2];
+			colon = strchr(id, ':');
+			if (!colon || colon = id)
+				goto invalid_ace;
+
+			*colon = 0;
+			if (strcmp(id, "all") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_EVERYONE;
+			} else if (strcmp(id, "grp") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_GROUP;
+			} else if (strcmp(id, "own") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_OWNER;
+			} else if (strcmp(id, "pos") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_POSSESSOR;
+			} else if (strcmp(id, "root") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_ROOT;
+			} else if (strcmp(id, "sys") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_SYS_ADMIN;
+			} else if (strcmp(id, "net") = 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_NET_ADMIN;
+			} else if (strncmp(id, "u.", 2) = 0) {
+				ace->mask = KEY_ACE_UID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->uid = tmp;
+			} else if (strncmp(id, "g.", 2) = 0) {
+				ace->mask = KEY_ACE_GID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->gid = tmp;
+			} else {
+				goto invalid_ace_colon;
+			}
+
+			perm = colon + 1;
+			if (isdigit(*perm)) {
+				ltmp = strtoul(perm, &p, 0);
+				if (ltmp & ~(unsigned long)KEY_ACE__PERMS)
+					goto invalid_ace_colon;
+				ace->mask |= ltmp;
+			} else if (strcmp(perm, "all") = 0) {
+				ace->mask |= KEY_ACE__PERMS;
+			} else {
+				for (; *perm; perm++) {
+					switch (*perm) {
+					case 'v': ace->mask |= KEY_ACE_VIEW;	break;
+					case 'r': ace->mask |= KEY_ACE_READ;	break;
+					case 'w': ace->mask |= KEY_ACE_WRITE;	break;
+					case 's': ace->mask |= KEY_ACE_SEARCH;	break;
+					case 'l': ace->mask |= KEY_ACE_LINK;	break;
+					case 'I': ace->mask |= KEY_ACE_INVAL;	break;
+					case 'R': ace->mask |= KEY_ACE_REVOKE;	break;
+					case 'S': ace->mask |= KEY_ACE_SET_SECURITY; break;
+					case 'j': ace->mask |= KEY_ACE_JOIN;	break;
+					case 'c': ace->mask |= KEY_ACE_CLEAR;	break;
+					default: goto invalid_ace_colon;
+					}
+				}
+			}
+		}
+	}
+
+	if (keyctl_set_acl(key, acl, nr_ace * sizeof(*acl)) < 0)
+		error("keyctl_set_acl");
+
+	exit(0);
+
+invalid_ace_colon:
+	*colon = ':';
+invalid_ace:
+	fprintf(stderr, "Invalid ACE: '%s'\n", id);
+	exit(2);
+}
+
 /*****************************************************************************/
 /*
  * parse a key identifier
diff --git a/keyutils.c b/keyutils.c
index d2bb34c..a37acbc 100644
--- a/keyutils.c
+++ b/keyutils.c
@@ -264,6 +264,16 @@ long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 	return keyctl(KEYCTL_RESTRICT_KEYRING, keyring, type, restriction);
 }
 
+long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size)
+{
+	return keyctl(KEYCTL_GET_ACL, id, acl, max_acl_size);
+}
+
+long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t acl_size)
+{
+	return keyctl(KEYCTL_SET_ACL, id, acl, acl_size);
+}
+
 /*****************************************************************************/
 /*
  * fetch key description into an allocated buffer
@@ -405,6 +415,41 @@ int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 	return ret;
 }
 
+/*****************************************************************************/
+/*
+ * Fetch key ACL into an allocated buffer
+ * - Returns size of ACL in bytes.
+ */
+int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl)
+{
+	struct key_ace *acl;
+	long acl_size, ret;
+
+	ret = keyctl_get_acl(id, NULL, 0);
+	if (ret < 0)
+		return -1;
+
+	for (;;) {
+		acl_size = ret;
+		acl = malloc(acl_size);
+		if (!acl)
+			return -1;
+
+		ret = keyctl_get_acl(id, acl, acl_size);
+		if (ret < 0) {
+			free(acl);
+			return -1;
+		}
+
+		if (acl_size >= ret)
+			break;
+		free(acl);
+	}
+
+	*_acl = acl;
+	return ret;
+}
+
 /*
  * Depth-first recursively apply a function over a keyring tree
  */
diff --git a/keyutils.h b/keyutils.h
index 89c5b08..de1b0b4 100644
--- a/keyutils.h
+++ b/keyutils.h
@@ -101,6 +101,8 @@ typedef uint32_t key_perm_t;
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
@@ -117,6 +119,42 @@ struct keyctl_kdf_params {
 };
 
 /*
+ * Key ACL definitions.
+ */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
+/*
  * syscall wrappers
  */
 extern key_serial_t add_key(const char *type,
@@ -177,6 +215,8 @@ extern long keyctl_dh_compute_kdf(key_serial_t private, key_serial_t prime,
 				  char *buffer, size_t buflen);
 extern long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 				    const char *restriction);
+extern long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size);
+extern long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t max_acl_size);
 
 /*
  * utilities
@@ -186,6 +226,7 @@ extern int keyctl_read_alloc(key_serial_t id, void **_buffer);
 extern int keyctl_get_security_alloc(key_serial_t id, char **_buffer);
 extern int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 				   key_serial_t base, void **_buffer);
+extern int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl);
 
 typedef int (*recursive_key_scanner_t)(key_serial_t parent, key_serial_t key,
 				       char *desc, int desc_len, void *data);
diff --git a/tests/keyctl/dh_compute/bad-args/runtest.sh b/tests/keyctl/dh_compute/bad-args/runtest.sh
index 7e8828b..f58a44c 100644
--- a/tests/keyctl/dh_compute/bad-args/runtest.sh
+++ b/tests/keyctl/dh_compute/bad-args/runtest.sh
@@ -72,7 +72,7 @@ expect_keyid logonid
 
 marker "CHECK WRONG KEY TYPE"
 dh_compute --fail $privateid $primeid $logonid
-expect_error ENOKEY
+expect_error EOPNOTSUPP
 dh_compute --fail $privateid $primeid @s
 expect_error EOPNOTSUPP
 
diff --git a/tests/keyctl/permitting/valid/runtest.sh b/tests/keyctl/permitting/valid/runtest.sh
index 70600e7..0b3d55f 100644
--- a/tests/keyctl/permitting/valid/runtest.sh
+++ b/tests/keyctl/permitting/valid/runtest.sh
@@ -72,12 +72,11 @@ set_key_perm $keyid 0x00201f00
 describe_key --fail $keyid
 expect_error EACCES
 
-# check that we can't use other perms instead of user perms to view the key
-# (our UID matches that of the key)
-marker "VIEW OTHER PERMISSIONS"
+# check that permissions for everyone allow the user to view the key,
+# even if our user perms don't.
+marker "VIEW EVERYONE PERMISSIONS"
 set_key_perm $keyid 0x0020001f
-describe_key --fail $keyid
-expect_error EACCES
+describe_key $keyid
 
 # check that taking away setattr permission renders the key immune to setperm
 marker "REMOVE SETATTR"

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] keyutils: Add key/keyring ACL support
@ 2017-09-27 16:10   ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 16:10 UTC (permalink / raw)
  To: Eric Biggers; +Cc: dhowells, keyrings, linux-security-module, linux-kernel

Here's a patch to make keyutils support ACLs on keys.

[root@andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl setacl $k all:all u.4:r pos:0x37f root:I net:c
[root@andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl getacl $k
IDENTITY  ID         PERMS
========= ========== ===================
all       -          0fffffff cjSRIlswrv
uid                4 00000002 --------r-
possessor -          0000037f cj-RIlswrv
root      -          00000020 ----I-----
net       -          00000200 c---------


David
---
diff --git a/keyctl.c b/keyctl.c
index 61990b4..c8b6f6f 100644
--- a/keyctl.c
+++ b/keyctl.c
@@ -71,6 +71,9 @@ static nr void act_keyctl_dh_compute(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf_oi(int argc, char *argv[]);
 static nr void act_keyctl_restrict_keyring(int argc, char *argv[]);
+static nr void act_keyctl_getacl(int argc, char *argv[]);
+static nr void act_keyctl_rgetacl(int argc, char *argv[]);
+static nr void act_keyctl_setacl(int argc, char *argv[]);
 
 const struct command commands[] = {
 	{ act_keyctl___version,	"--version",	"" },
@@ -82,9 +85,10 @@ const struct command commands[] = {
 	{ act_keyctl_dh_compute, "dh_compute",	"<private> <prime> <base>" },
 	{ act_keyctl_dh_compute_kdf, "dh_compute_kdf", "<private> <prime> <base> <len> <hash_name>" },
 	{ act_keyctl_dh_compute_kdf_oi, "dh_compute_kdf_oi", "<private> <prime> <base> <len> <hash_name>" },
+	{ act_keyctl_getacl,	"getacl",	"<key>" },
+	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_instantiate, "instantiate","<key> <data> <keyring>" },
 	{ act_keyctl_invalidate,"invalidate",	"<key>" },
-	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_link,	"link",		"<key> <keyring>" },
 	{ act_keyctl_list,	"list",		"<keyring>" },
 	{ act_keyctl_negate,	"negate",	"<key> <timeout> <keyring>" },
@@ -107,12 +111,14 @@ const struct command commands[] = {
 	{ act_keyctl_request2,	"request2",	"<type> <desc> <info> [<dest_keyring>]" },
 	{ act_keyctl_restrict_keyring, "restrict_keyring", "<keyring> [<type> [<restriction>]]" },
 	{ act_keyctl_revoke,	"revoke",	"<key>" },
+	{ act_keyctl_rgetacl,	"rgetacl",	"<key>" },
 	{ act_keyctl_rlist,	"rlist",	"<keyring>" },
 	{ act_keyctl_search,	"search",	"<keyring> <type> <desc> [<dest_keyring>]" },
 	{ act_keyctl_security,	"security",	"<key>" },
 	{ act_keyctl_session,	"session",	"" },
 	{ NULL,			"session",	"- [<prog> <arg1> <arg2> ...]" },
 	{ NULL,			"session",	"<name> [<prog> <arg1> <arg2> ...]" },
+	{ act_keyctl_setacl,	"setacl",	"<key> [<ace> <ace> ...]" },
 	{ act_keyctl_setperm,	"setperm",	"<key> <mask>" },
 	{ act_keyctl_show,	"show",		"[-x] [<keyring>]" },
 	{ act_keyctl_timeout,	"timeout",	"<key> <timeout>" },
@@ -1841,6 +1847,226 @@ static void act_keyctl_restrict_keyring(int argc, char *argv[])
 	exit(0);
 }
 
+static const char *special_ace_ids[] = {
+	[0]			= "?0",
+	[KEY_ACE_EVERYONE]	= "all",
+	[KEY_ACE_GROUP]		= "group",
+	[KEY_ACE_OWNER]		= "owner",
+	[KEY_ACE_POSSESSOR]	= "possessor",
+	[KEY_ACE_ROOT]		= "root",
+	[KEY_ACE_SYS_ADMIN]	= "sys",
+	[KEY_ACE_NET_ADMIN]	= "net",
+};
+
+/*
+ * Display ACL.
+ */
+static void display_acl(const struct key_ace *acl, int nr_ace)
+{
+	int i;
+
+	printf("IDENTITY  ID         PERMS\n");
+	printf("========= ========== ===================\n");
+
+	for (i = 0; i < nr_ace; i++) {
+		const struct key_ace *ace = &acl[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (ace->special_id <= KEY_ACE_NET_ADMIN)
+				printf("%-9.9s -         ", special_ace_ids[ace->special_id]);
+			else
+				printf("?special  %10u", ace->special_id);
+			break;
+		case KEY_ACE_UID:
+			printf("uid       %10u", ace->uid);
+			break;
+		case KEY_ACE_GID:
+			printf("gid       %10u", ace->gid);
+			break;
+		default:
+			printf("?%x        %10u",
+			       (ace->mask & KEY_ACE__IDENTITY) >> 28,
+			       ace->special_id);
+			break;
+		}
+
+		printf(" %08x ", ace->mask & KEY_ACE__PERMS);
+		printf("%c%c%c%c%c%c%c%c%c%c\n",
+		       ace->mask & KEY_ACE_CLEAR	? 'c' : '-',
+		       ace->mask & KEY_ACE_JOIN		? 'j' : '-',
+		       ace->mask & KEY_ACE_SET_SECURITY	? 'S' : '-',
+		       ace->mask & KEY_ACE_REVOKE	? 'R' : '-',
+		       ace->mask & KEY_ACE_INVAL	? 'I' : '-',
+		       ace->mask & KEY_ACE_LINK		? 'l' : '-',
+		       ace->mask & KEY_ACE_SEARCH	? 's' : '-',
+		       ace->mask & KEY_ACE_WRITE	? 'w' : '-',
+		       ace->mask & KEY_ACE_READ		? 'r' : '-',
+		       ace->mask & KEY_ACE_VIEW		? 'v' : '-');
+	}
+}
+
+/*
+ * Get a key's ACL.
+ */
+static void act_keyctl_getacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	display_acl(acl, nr_ace);
+	exit(0);
+}
+
+/*
+ * Get a key's ACL in raw hex form.
+ */
+static void act_keyctl_rgetacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace, i;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	for (i = 0; i < nr_ace; i++) {
+		if (i != 0)
+			putchar(';');
+		printf("%x:%x", acl[i].mask, acl[i].special_id);
+	}
+
+	putchar('\n');
+	exit(0);
+}
+
+/*
+ * Set a key's ACL.
+ */
+static void act_keyctl_setacl(int argc, char *argv[])
+{
+	struct key_ace *acl = NULL, *ace;
+	key_serial_t key;
+	unsigned ltmp;
+	unsigned tmp, len;
+	char *id, *colon, *perm, *p;
+	int nr_ace, i;
+
+	if (argc < 2)
+		format();
+
+	key = get_key_id(argv[1]);
+	nr_ace = argc - 2;
+
+	if (nr_ace > 0) {
+		acl = alloca(sizeof(*acl) * nr_ace);
+		memset(acl, 0, sizeof(*acl) * nr_ace);
+
+		for (i = 0; i < nr_ace; i++) {
+			ace = &acl[i];
+			id = argv[i + 2];
+			colon = strchr(id, ':');
+			if (!colon || colon == id)
+				goto invalid_ace;
+
+			*colon = 0;
+			if (strcmp(id, "all") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_EVERYONE;
+			} else if (strcmp(id, "grp") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_GROUP;
+			} else if (strcmp(id, "own") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_OWNER;
+			} else if (strcmp(id, "pos") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_POSSESSOR;
+			} else if (strcmp(id, "root") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_ROOT;
+			} else if (strcmp(id, "sys") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_SYS_ADMIN;
+			} else if (strcmp(id, "net") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_NET_ADMIN;
+			} else if (strncmp(id, "u.", 2) == 0) {
+				ace->mask = KEY_ACE_UID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->uid = tmp;
+			} else if (strncmp(id, "g.", 2) == 0) {
+				ace->mask = KEY_ACE_GID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->gid = tmp;
+			} else {
+				goto invalid_ace_colon;
+			}
+
+			perm = colon + 1;
+			if (isdigit(*perm)) {
+				ltmp = strtoul(perm, &p, 0);
+				if (ltmp & ~(unsigned long)KEY_ACE__PERMS)
+					goto invalid_ace_colon;
+				ace->mask |= ltmp;
+			} else if (strcmp(perm, "all") == 0) {
+				ace->mask |= KEY_ACE__PERMS;
+			} else {
+				for (; *perm; perm++) {
+					switch (*perm) {
+					case 'v': ace->mask |= KEY_ACE_VIEW;	break;
+					case 'r': ace->mask |= KEY_ACE_READ;	break;
+					case 'w': ace->mask |= KEY_ACE_WRITE;	break;
+					case 's': ace->mask |= KEY_ACE_SEARCH;	break;
+					case 'l': ace->mask |= KEY_ACE_LINK;	break;
+					case 'I': ace->mask |= KEY_ACE_INVAL;	break;
+					case 'R': ace->mask |= KEY_ACE_REVOKE;	break;
+					case 'S': ace->mask |= KEY_ACE_SET_SECURITY; break;
+					case 'j': ace->mask |= KEY_ACE_JOIN;	break;
+					case 'c': ace->mask |= KEY_ACE_CLEAR;	break;
+					default: goto invalid_ace_colon;
+					}
+				}
+			}
+		}
+	}
+
+	if (keyctl_set_acl(key, acl, nr_ace * sizeof(*acl)) < 0)
+		error("keyctl_set_acl");
+
+	exit(0);
+
+invalid_ace_colon:
+	*colon = ':';
+invalid_ace:
+	fprintf(stderr, "Invalid ACE: '%s'\n", id);
+	exit(2);
+}
+
 /*****************************************************************************/
 /*
  * parse a key identifier
diff --git a/keyutils.c b/keyutils.c
index d2bb34c..a37acbc 100644
--- a/keyutils.c
+++ b/keyutils.c
@@ -264,6 +264,16 @@ long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 	return keyctl(KEYCTL_RESTRICT_KEYRING, keyring, type, restriction);
 }
 
+long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size)
+{
+	return keyctl(KEYCTL_GET_ACL, id, acl, max_acl_size);
+}
+
+long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t acl_size)
+{
+	return keyctl(KEYCTL_SET_ACL, id, acl, acl_size);
+}
+
 /*****************************************************************************/
 /*
  * fetch key description into an allocated buffer
@@ -405,6 +415,41 @@ int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 	return ret;
 }
 
+/*****************************************************************************/
+/*
+ * Fetch key ACL into an allocated buffer
+ * - Returns size of ACL in bytes.
+ */
+int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl)
+{
+	struct key_ace *acl;
+	long acl_size, ret;
+
+	ret = keyctl_get_acl(id, NULL, 0);
+	if (ret < 0)
+		return -1;
+
+	for (;;) {
+		acl_size = ret;
+		acl = malloc(acl_size);
+		if (!acl)
+			return -1;
+
+		ret = keyctl_get_acl(id, acl, acl_size);
+		if (ret < 0) {
+			free(acl);
+			return -1;
+		}
+
+		if (acl_size >= ret)
+			break;
+		free(acl);
+	}
+
+	*_acl = acl;
+	return ret;
+}
+
 /*
  * Depth-first recursively apply a function over a keyring tree
  */
diff --git a/keyutils.h b/keyutils.h
index 89c5b08..de1b0b4 100644
--- a/keyutils.h
+++ b/keyutils.h
@@ -101,6 +101,8 @@ typedef uint32_t key_perm_t;
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
@@ -117,6 +119,42 @@ struct keyctl_kdf_params {
 };
 
 /*
+ * Key ACL definitions.
+ */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
+/*
  * syscall wrappers
  */
 extern key_serial_t add_key(const char *type,
@@ -177,6 +215,8 @@ extern long keyctl_dh_compute_kdf(key_serial_t private, key_serial_t prime,
 				  char *buffer, size_t buflen);
 extern long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 				    const char *restriction);
+extern long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size);
+extern long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t max_acl_size);
 
 /*
  * utilities
@@ -186,6 +226,7 @@ extern int keyctl_read_alloc(key_serial_t id, void **_buffer);
 extern int keyctl_get_security_alloc(key_serial_t id, char **_buffer);
 extern int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 				   key_serial_t base, void **_buffer);
+extern int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl);
 
 typedef int (*recursive_key_scanner_t)(key_serial_t parent, key_serial_t key,
 				       char *desc, int desc_len, void *data);
diff --git a/tests/keyctl/dh_compute/bad-args/runtest.sh b/tests/keyctl/dh_compute/bad-args/runtest.sh
index 7e8828b..f58a44c 100644
--- a/tests/keyctl/dh_compute/bad-args/runtest.sh
+++ b/tests/keyctl/dh_compute/bad-args/runtest.sh
@@ -72,7 +72,7 @@ expect_keyid logonid
 
 marker "CHECK WRONG KEY TYPE"
 dh_compute --fail $privateid $primeid $logonid
-expect_error ENOKEY
+expect_error EOPNOTSUPP
 dh_compute --fail $privateid $primeid @s
 expect_error EOPNOTSUPP
 
diff --git a/tests/keyctl/permitting/valid/runtest.sh b/tests/keyctl/permitting/valid/runtest.sh
index 70600e7..0b3d55f 100644
--- a/tests/keyctl/permitting/valid/runtest.sh
+++ b/tests/keyctl/permitting/valid/runtest.sh
@@ -72,12 +72,11 @@ set_key_perm $keyid 0x00201f00
 describe_key --fail $keyid
 expect_error EACCES
 
-# check that we can't use other perms instead of user perms to view the key
-# (our UID matches that of the key)
-marker "VIEW OTHER PERMISSIONS"
+# check that permissions for everyone allow the user to view the key,
+# even if our user perms don't.
+marker "VIEW EVERYONE PERMISSIONS"
 set_key_perm $keyid 0x0020001f
-describe_key --fail $keyid
-expect_error EACCES
+describe_key $keyid
 
 # check that taking away setattr permission renders the key immune to setperm
 marker "REMOVE SETATTR"

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] keyutils: Add key/keyring ACL support
@ 2017-09-27 16:10   ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-09-27 16:10 UTC (permalink / raw)
  To: linux-security-module

Here's a patch to make keyutils support ACLs on keys.

[root at andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl setacl $k all:all u.4:r pos:0x37f root:I net:c
[root at andromeda ~]# LD_LIBRARY_PATH=~/keyutils ~/keyutils/keyctl getacl $k
IDENTITY  ID         PERMS
========= ========== ===================
all       -          0fffffff cjSRIlswrv
uid                4 00000002 --------r-
possessor -          0000037f cj-RIlswrv
root      -          00000020 ----I-----
net       -          00000200 c---------


David
---
diff --git a/keyctl.c b/keyctl.c
index 61990b4..c8b6f6f 100644
--- a/keyctl.c
+++ b/keyctl.c
@@ -71,6 +71,9 @@ static nr void act_keyctl_dh_compute(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf(int argc, char *argv[]);
 static nr void act_keyctl_dh_compute_kdf_oi(int argc, char *argv[]);
 static nr void act_keyctl_restrict_keyring(int argc, char *argv[]);
+static nr void act_keyctl_getacl(int argc, char *argv[]);
+static nr void act_keyctl_rgetacl(int argc, char *argv[]);
+static nr void act_keyctl_setacl(int argc, char *argv[]);
 
 const struct command commands[] = {
 	{ act_keyctl___version,	"--version",	"" },
@@ -82,9 +85,10 @@ const struct command commands[] = {
 	{ act_keyctl_dh_compute, "dh_compute",	"<private> <prime> <base>" },
 	{ act_keyctl_dh_compute_kdf, "dh_compute_kdf", "<private> <prime> <base> <len> <hash_name>" },
 	{ act_keyctl_dh_compute_kdf_oi, "dh_compute_kdf_oi", "<private> <prime> <base> <len> <hash_name>" },
+	{ act_keyctl_getacl,	"getacl",	"<key>" },
+	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_instantiate, "instantiate","<key> <data> <keyring>" },
 	{ act_keyctl_invalidate,"invalidate",	"<key>" },
-	{ act_keyctl_get_persistent, "get_persistent", "<keyring> [<uid>]" },
 	{ act_keyctl_link,	"link",		"<key> <keyring>" },
 	{ act_keyctl_list,	"list",		"<keyring>" },
 	{ act_keyctl_negate,	"negate",	"<key> <timeout> <keyring>" },
@@ -107,12 +111,14 @@ const struct command commands[] = {
 	{ act_keyctl_request2,	"request2",	"<type> <desc> <info> [<dest_keyring>]" },
 	{ act_keyctl_restrict_keyring, "restrict_keyring", "<keyring> [<type> [<restriction>]]" },
 	{ act_keyctl_revoke,	"revoke",	"<key>" },
+	{ act_keyctl_rgetacl,	"rgetacl",	"<key>" },
 	{ act_keyctl_rlist,	"rlist",	"<keyring>" },
 	{ act_keyctl_search,	"search",	"<keyring> <type> <desc> [<dest_keyring>]" },
 	{ act_keyctl_security,	"security",	"<key>" },
 	{ act_keyctl_session,	"session",	"" },
 	{ NULL,			"session",	"- [<prog> <arg1> <arg2> ...]" },
 	{ NULL,			"session",	"<name> [<prog> <arg1> <arg2> ...]" },
+	{ act_keyctl_setacl,	"setacl",	"<key> [<ace> <ace> ...]" },
 	{ act_keyctl_setperm,	"setperm",	"<key> <mask>" },
 	{ act_keyctl_show,	"show",		"[-x] [<keyring>]" },
 	{ act_keyctl_timeout,	"timeout",	"<key> <timeout>" },
@@ -1841,6 +1847,226 @@ static void act_keyctl_restrict_keyring(int argc, char *argv[])
 	exit(0);
 }
 
+static const char *special_ace_ids[] = {
+	[0]			= "?0",
+	[KEY_ACE_EVERYONE]	= "all",
+	[KEY_ACE_GROUP]		= "group",
+	[KEY_ACE_OWNER]		= "owner",
+	[KEY_ACE_POSSESSOR]	= "possessor",
+	[KEY_ACE_ROOT]		= "root",
+	[KEY_ACE_SYS_ADMIN]	= "sys",
+	[KEY_ACE_NET_ADMIN]	= "net",
+};
+
+/*
+ * Display ACL.
+ */
+static void display_acl(const struct key_ace *acl, int nr_ace)
+{
+	int i;
+
+	printf("IDENTITY  ID         PERMS\n");
+	printf("========= ========== ===================\n");
+
+	for (i = 0; i < nr_ace; i++) {
+		const struct key_ace *ace = &acl[i];
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (ace->special_id <= KEY_ACE_NET_ADMIN)
+				printf("%-9.9s -         ", special_ace_ids[ace->special_id]);
+			else
+				printf("?special  %10u", ace->special_id);
+			break;
+		case KEY_ACE_UID:
+			printf("uid       %10u", ace->uid);
+			break;
+		case KEY_ACE_GID:
+			printf("gid       %10u", ace->gid);
+			break;
+		default:
+			printf("?%x        %10u",
+			       (ace->mask & KEY_ACE__IDENTITY) >> 28,
+			       ace->special_id);
+			break;
+		}
+
+		printf(" %08x ", ace->mask & KEY_ACE__PERMS);
+		printf("%c%c%c%c%c%c%c%c%c%c\n",
+		       ace->mask & KEY_ACE_CLEAR	? 'c' : '-',
+		       ace->mask & KEY_ACE_JOIN		? 'j' : '-',
+		       ace->mask & KEY_ACE_SET_SECURITY	? 'S' : '-',
+		       ace->mask & KEY_ACE_REVOKE	? 'R' : '-',
+		       ace->mask & KEY_ACE_INVAL	? 'I' : '-',
+		       ace->mask & KEY_ACE_LINK		? 'l' : '-',
+		       ace->mask & KEY_ACE_SEARCH	? 's' : '-',
+		       ace->mask & KEY_ACE_WRITE	? 'w' : '-',
+		       ace->mask & KEY_ACE_READ		? 'r' : '-',
+		       ace->mask & KEY_ACE_VIEW		? 'v' : '-');
+	}
+}
+
+/*
+ * Get a key's ACL.
+ */
+static void act_keyctl_getacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	display_acl(acl, nr_ace);
+	exit(0);
+}
+
+/*
+ * Get a key's ACL in raw hex form.
+ */
+static void act_keyctl_rgetacl(int argc, char *argv[])
+{
+	struct key_ace *acl;
+	key_serial_t key;
+	int ret, nr_ace, i;
+
+	if (argc != 2)
+		format();
+
+	key = get_key_id(argv[1]);
+
+	ret = keyctl_get_acl_alloc(key, &acl);
+	if (ret < 0)
+		error("keyctl_get_acl_alloc");
+	nr_ace = ret / sizeof(*acl);
+
+	for (i = 0; i < nr_ace; i++) {
+		if (i != 0)
+			putchar(';');
+		printf("%x:%x", acl[i].mask, acl[i].special_id);
+	}
+
+	putchar('\n');
+	exit(0);
+}
+
+/*
+ * Set a key's ACL.
+ */
+static void act_keyctl_setacl(int argc, char *argv[])
+{
+	struct key_ace *acl = NULL, *ace;
+	key_serial_t key;
+	unsigned ltmp;
+	unsigned tmp, len;
+	char *id, *colon, *perm, *p;
+	int nr_ace, i;
+
+	if (argc < 2)
+		format();
+
+	key = get_key_id(argv[1]);
+	nr_ace = argc - 2;
+
+	if (nr_ace > 0) {
+		acl = alloca(sizeof(*acl) * nr_ace);
+		memset(acl, 0, sizeof(*acl) * nr_ace);
+
+		for (i = 0; i < nr_ace; i++) {
+			ace = &acl[i];
+			id = argv[i + 2];
+			colon = strchr(id, ':');
+			if (!colon || colon == id)
+				goto invalid_ace;
+
+			*colon = 0;
+			if (strcmp(id, "all") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_EVERYONE;
+			} else if (strcmp(id, "grp") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_GROUP;
+			} else if (strcmp(id, "own") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_OWNER;
+			} else if (strcmp(id, "pos") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_POSSESSOR;
+			} else if (strcmp(id, "root") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_ROOT;
+			} else if (strcmp(id, "sys") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_SYS_ADMIN;
+			} else if (strcmp(id, "net") == 0) {
+				ace->mask = KEY_ACE_SPECIAL;
+				ace->special_id = KEY_ACE_NET_ADMIN;
+			} else if (strncmp(id, "u.", 2) == 0) {
+				ace->mask = KEY_ACE_UID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->uid = tmp;
+			} else if (strncmp(id, "g.", 2) == 0) {
+				ace->mask = KEY_ACE_GID;
+				if (sscanf(id + 2, "%u%n", &tmp, &len) != 1)
+					goto invalid_ace_colon;
+				if (id + 2 + len != colon)
+					goto invalid_ace_colon;
+				ace->gid = tmp;
+			} else {
+				goto invalid_ace_colon;
+			}
+
+			perm = colon + 1;
+			if (isdigit(*perm)) {
+				ltmp = strtoul(perm, &p, 0);
+				if (ltmp & ~(unsigned long)KEY_ACE__PERMS)
+					goto invalid_ace_colon;
+				ace->mask |= ltmp;
+			} else if (strcmp(perm, "all") == 0) {
+				ace->mask |= KEY_ACE__PERMS;
+			} else {
+				for (; *perm; perm++) {
+					switch (*perm) {
+					case 'v': ace->mask |= KEY_ACE_VIEW;	break;
+					case 'r': ace->mask |= KEY_ACE_READ;	break;
+					case 'w': ace->mask |= KEY_ACE_WRITE;	break;
+					case 's': ace->mask |= KEY_ACE_SEARCH;	break;
+					case 'l': ace->mask |= KEY_ACE_LINK;	break;
+					case 'I': ace->mask |= KEY_ACE_INVAL;	break;
+					case 'R': ace->mask |= KEY_ACE_REVOKE;	break;
+					case 'S': ace->mask |= KEY_ACE_SET_SECURITY; break;
+					case 'j': ace->mask |= KEY_ACE_JOIN;	break;
+					case 'c': ace->mask |= KEY_ACE_CLEAR;	break;
+					default: goto invalid_ace_colon;
+					}
+				}
+			}
+		}
+	}
+
+	if (keyctl_set_acl(key, acl, nr_ace * sizeof(*acl)) < 0)
+		error("keyctl_set_acl");
+
+	exit(0);
+
+invalid_ace_colon:
+	*colon = ':';
+invalid_ace:
+	fprintf(stderr, "Invalid ACE: '%s'\n", id);
+	exit(2);
+}
+
 /*****************************************************************************/
 /*
  * parse a key identifier
diff --git a/keyutils.c b/keyutils.c
index d2bb34c..a37acbc 100644
--- a/keyutils.c
+++ b/keyutils.c
@@ -264,6 +264,16 @@ long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 	return keyctl(KEYCTL_RESTRICT_KEYRING, keyring, type, restriction);
 }
 
+long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size)
+{
+	return keyctl(KEYCTL_GET_ACL, id, acl, max_acl_size);
+}
+
+long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t acl_size)
+{
+	return keyctl(KEYCTL_SET_ACL, id, acl, acl_size);
+}
+
 /*****************************************************************************/
 /*
  * fetch key description into an allocated buffer
@@ -405,6 +415,41 @@ int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 	return ret;
 }
 
+/*****************************************************************************/
+/*
+ * Fetch key ACL into an allocated buffer
+ * - Returns size of ACL in bytes.
+ */
+int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl)
+{
+	struct key_ace *acl;
+	long acl_size, ret;
+
+	ret = keyctl_get_acl(id, NULL, 0);
+	if (ret < 0)
+		return -1;
+
+	for (;;) {
+		acl_size = ret;
+		acl = malloc(acl_size);
+		if (!acl)
+			return -1;
+
+		ret = keyctl_get_acl(id, acl, acl_size);
+		if (ret < 0) {
+			free(acl);
+			return -1;
+		}
+
+		if (acl_size >= ret)
+			break;
+		free(acl);
+	}
+
+	*_acl = acl;
+	return ret;
+}
+
 /*
  * Depth-first recursively apply a function over a keyring tree
  */
diff --git a/keyutils.h b/keyutils.h
index 89c5b08..de1b0b4 100644
--- a/keyutils.h
+++ b/keyutils.h
@@ -101,6 +101,8 @@ typedef uint32_t key_perm_t;
 #define KEYCTL_GET_PERSISTENT		22	/* get a user's persistent keyring */
 #define KEYCTL_DH_COMPUTE		23	/* Compute Diffie-Hellman values */
 #define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
+#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
+#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
 
 /* keyctl structures */
 struct keyctl_dh_params {
@@ -117,6 +119,42 @@ struct keyctl_kdf_params {
 };
 
 /*
+ * Key ACL definitions.
+ */
+struct key_ace {
+	unsigned int		mask;
+#define KEY_ACE_VIEW		0x00000001 /* Can describe the key */
+#define KEY_ACE_READ		0x00000002 /* Can read the key content */
+#define KEY_ACE_WRITE		0x00000004 /* Can update/modify the key content */
+#define KEY_ACE_SEARCH		0x00000008 /* Can find the key by search */
+#define KEY_ACE_LINK		0x00000010 /* Can make a link to the key */
+#define KEY_ACE_INVAL		0x00000020 /* Can invalidate the key */
+#define KEY_ACE_REVOKE		0x00000040 /* Can revoke the key */
+#define KEY_ACE_SET_SECURITY	0x00000080 /* Can set owner, group, ACL */
+#define KEY_ACE_JOIN		0x00000100 /* Can join keyring */
+#define KEY_ACE_CLEAR		0x00000200 /* Can clear keyring */
+#define KEY_ACE__ORDINARY	0x0000001f /* Ordinary permissions */
+#define KEY_ACE__PERMS		0x0fffffff
+#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
+#define KEY_ACE_UID		0x20000000 /* A nominated UID */
+#define KEY_ACE_GID		0x30000000 /* A nominated GID */
+#define KEY_ACE__IDENTITY	0xf0000000
+
+	union {
+		uid_t		uid;
+		gid_t		gid;
+		unsigned int	special_id;
+#define KEY_ACE_EVERYONE	1	/* Everyone, including owner and group */
+#define KEY_ACE_GROUP		2	/* The key's group */
+#define KEY_ACE_OWNER		3	/* The owner of the key */
+#define KEY_ACE_POSSESSOR	4	/* Any process that possesses of the key */
+#define KEY_ACE_ROOT		5	/* The user namespace root user */
+#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
+#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
+	};
+};
+
+/*
  * syscall wrappers
  */
 extern key_serial_t add_key(const char *type,
@@ -177,6 +215,8 @@ extern long keyctl_dh_compute_kdf(key_serial_t private, key_serial_t prime,
 				  char *buffer, size_t buflen);
 extern long keyctl_restrict_keyring(key_serial_t keyring, const char *type,
 				    const char *restriction);
+extern long keyctl_get_acl(key_serial_t id, struct key_ace *acl, size_t max_acl_size);
+extern long keyctl_set_acl(key_serial_t id, const struct key_ace *acl, size_t max_acl_size);
 
 /*
  * utilities
@@ -186,6 +226,7 @@ extern int keyctl_read_alloc(key_serial_t id, void **_buffer);
 extern int keyctl_get_security_alloc(key_serial_t id, char **_buffer);
 extern int keyctl_dh_compute_alloc(key_serial_t priv, key_serial_t prime,
 				   key_serial_t base, void **_buffer);
+extern int keyctl_get_acl_alloc(key_serial_t id, struct key_ace **_acl);
 
 typedef int (*recursive_key_scanner_t)(key_serial_t parent, key_serial_t key,
 				       char *desc, int desc_len, void *data);
diff --git a/tests/keyctl/dh_compute/bad-args/runtest.sh b/tests/keyctl/dh_compute/bad-args/runtest.sh
index 7e8828b..f58a44c 100644
--- a/tests/keyctl/dh_compute/bad-args/runtest.sh
+++ b/tests/keyctl/dh_compute/bad-args/runtest.sh
@@ -72,7 +72,7 @@ expect_keyid logonid
 
 marker "CHECK WRONG KEY TYPE"
 dh_compute --fail $privateid $primeid $logonid
-expect_error ENOKEY
+expect_error EOPNOTSUPP
 dh_compute --fail $privateid $primeid @s
 expect_error EOPNOTSUPP
 
diff --git a/tests/keyctl/permitting/valid/runtest.sh b/tests/keyctl/permitting/valid/runtest.sh
index 70600e7..0b3d55f 100644
--- a/tests/keyctl/permitting/valid/runtest.sh
+++ b/tests/keyctl/permitting/valid/runtest.sh
@@ -72,12 +72,11 @@ set_key_perm $keyid 0x00201f00
 describe_key --fail $keyid
 expect_error EACCES
 
-# check that we can't use other perms instead of user perms to view the key
-# (our UID matches that of the key)
-marker "VIEW OTHER PERMISSIONS"
+# check that permissions for everyone allow the user to view the key,
+# even if our user perms don't.
+marker "VIEW EVERYONE PERMISSIONS"
 set_key_perm $keyid 0x0020001f
-describe_key --fail $keyid
-expect_error EACCES
+describe_key $keyid
 
 # check that taking away setattr permission renders the key immune to setperm
 marker "REMOVE SETATTR"
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
  2017-09-27 11:41   ` David Howells
  (?)
@ 2017-10-02  2:59     ` Eric Biggers
  -1 siblings, 0 replies; 15+ messages in thread
From: Eric Biggers @ 2017-10-02  2:59 UTC (permalink / raw)
  To: linux-security-module

Hi David,

On Wed, Sep 27, 2017 at 12:41:41PM +0100, David Howells wrote:
>     
>     Replace the uid/gid/perm permissions checking on a key with an ACL to allow
>     the SETATTR permission to be split.  The problem is that SETATTR covers a
>     slew of things, not all of which should be grouped together.  This
>     includes:
>     
>      (1) Changing the key ownership.
>     
>      (2) Changing the security information.
>     
>      (3) Keyring restriction.
>     
>      (4) Expiry time.
>     
>      (5) Revocation.
>     
>     and it has also been proposed to add:
>     
>      (6) Invalidation.
>     
>     The above can be divided into three groups: Controlling access (1), (2) and
>     (3), managing the content at construction time (4) and managing the key (5)
>     and (6).

This is interesting work, though it adds complexity and makes a lot of subtle
(and potentially breaking) changes to which permissions are required for various
things.  First I think you need to start out with a better statement of the
problems you are trying to solve.  The patch does much more than simply split up
the SETATTR permission --- for example, it also adds the ability to assign
permissions to specific uids, gids, and capabilities.  Who is planning to use
those features and why?

>     The KEYCTL_SETATTR function is then deprecated.  If called, it will

KEYCTL_SETPERM

>     construct an ACL to reflect the mask it is given, using possessor, owner,
>     group and other ACE's as appropriate if any of those elements are granted
>     any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
>     SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
>     CLEAR.  JOIN is turned on if a keyring is being altered.

The proposed changes to keyctl_setperm_key() actually never enable INVAL at all,
which doesn't match the description here.  Also, all breaking changes need to be
justified.  If keyctl_setperm(key, KEY_*_SEARCH) is no longer going to allow the
key to be invalidated (as I had proposed earlier), that is really its own change
which needs its own justification; it shouldn't be hidden in a larger patch.

>     will return an error if SETACL has been called on a key.

That is simplest, but it doesn't match the behavior of POSIX ACLs, for example.
With POSIX ACLs you can still chmod() a file that has an ACL.

>     The KEYCTL_DESCRIBE function then creates a permissions mask to return
>     depending on possessor, owner, group and other ACEs, indicating SETATTR if
>     any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
>     of WRITE, REVOKE or CLEAR are set.

Ignoring ACEs for specific users, groups, and capabilities may be problematic
because the returned mask will under-estimate rather than over-estimate the
permissions that have been granted.  With POSIX ACLs, for example, the union of
all permissions that have been granted to any subjects other than the regular
ones is reflected in the group entry.  I believe that's generally considered
better from a security perspective, because then no permissions are "hidden"
from a listing of the regular (non-ACL) permissions only.

>     Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
>     the value set with KEYCTL_SETATTR - but this is already true because keys
>     that lack ->read() can't have READ set and keys that lack ->write() can't
>     have WRITE set.

Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
that lacks ->update().  They are only omitted from the default permissions.

>     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
>     set, or if the caller has a valid instantiation auth token.

This doesn't match the code, which asks for WRITE permission only.  It's also a
breaking change which needs to be justified on its own.  Also I'm not sure that
WRITE permission actually makes sense, given that KEYCTL_SET_TIMEOUT doesn't
modify the payload of the key.

> +static struct key_acl blacklist_key_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

Designators into flexible arrays are a gcc extension which doesn't work with
clang.  Use this instead:

	.aces = {
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
			.special_id = KEY_ACE_POSSESSOR,
		},
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
			.special_id = KEY_ACE_OWNER,
		},
	},

It's also difficult to read these lists of ACEs.  An ACE should read as "who",
then "what".  But now it reads as "part of who", then "what", then "the rest of
who".  It may be helpful to define some macros to create the entries more
concisely:

	.aces = {
		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
	}

> diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
> new file mode 100644
> index 000000000000..7a738569a673
> --- /dev/null
> +++ b/include/linux/keyctl.h
> @@ -0,0 +1,3 @@
> +#define key_ace user_key_ace
> +#include <uapi/linux/keyctl.h>
> +#undef key_ace

This is ugly.  How about using kernel_key_ace for the internal kernel version?

> +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */

What is a "specific subject"?

> +#define KEY_ACE_ROOT		5	/* The user namespace root user */

Which user namespace?

> +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */

In what user namespace?

> +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */

The implementations of these don't seem to be included in the patch.

> @@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
>  	struct key *keyring;
>  	long ret;
>  
> -	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
> +	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
>  	if (IS_ERR(keyring_ref)) {
>  		ret = PTR_ERR(keyring_ref);
> -
> -		/* Root is permitted to invalidate certain special keyrings */
> -		if (capable(CAP_SYS_ADMIN)) {
> -			keyring_ref = lookup_user_key(ringid, 0, 0);
> -			if (IS_ERR(keyring_ref))
> -				goto error;
> -			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
> -				     &key_ref_to_ptr(keyring_ref)->flags))
> -				goto clear;
> -			goto error_put;
> -		}
> -
>  		goto error;
>  	}

The CLEAR permission is weird because WRITE is a superset of it.  (Clearing a
keyring is equivalent to unlinking all keys in it.)  Permissions should be
orthogonal.  Did you consider instead having one permission for adding links to
a keyring and one for deleting links?  I'm thinking about use cases similar to
that which the sticky bit on files is used for...

> +	for (i = 0; i < 4; i++) {
> +		struct key_ace *ace = &acl->aces[j];
> +		unsigned int subset = (perm >> (i * 8)) & 0x3f;
> +
> +		if (!subset)
> +			continue;
> +		ace->special_id = KEY_ACE_EVERYONE + i;
> +		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);

No magic numbers please.  What are 0x3f and 0x1f?

Also, this code is assuming that the subject values are numbered in a certain
way.  There should be some BUILD_BUG_ON()s to verify that.

> +		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
> +			ace->mask |= KEY_ACE_REVOKE;
> +		if (subset & KEY_OTH_SETATTR)
> +			ace->mask |= KEY_ACE_SET_SECURITY;
> +		if (key->type = &key_type_keyring) {
> +			ace->mask |= KEY_ACE_JOIN;

Why is JOIN permission always given to keyrings?  If someone has JOIN permission
(or LINK permission, for that matter) on a keyring than they can acquire the
possessor permissions.  So always giving JOIN defeats the point of having all
the different non-possessor permissions...

> +			if (subset & KEY_OTH_WRITE)
> +				ace->mask |= KEY_ACE_CLEAR;
> +		}

Granting INVAL permission is missing.

> @@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
>  	long ret;
>  
>  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> -				  KEY_NEED_SETATTR);
> +				  KEY_NEED_WRITE);

As mentioned earlier, this is a breaking change which needs to be justified on
its own.

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	if (!acl || acl->nr_ace = 0)
> +		goto no_access_rcu;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				if (is_key_possessed(key_ref))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_OWNER:
> +				if (uid_eq(key->uid, cred->fsuid))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_GROUP:
> +				if (gid_valid(key->gid) &&
> +				    gid_eq(key->gid, cred->fsgid))
> +					allow |= ace->mask;
> +				else if (groups_search(cred->group_info, key->gid))
> +					allow |= ace->mask;

Why call groups_search() when the key gid isn't valid?

> +			case KEY_ACE_ROOT:
> +				if (uid_eq(key->uid, cred->user_ns->owner))
> +					allow |= ace->mask;
> +				break;

Not sure I understand this.  So if someone owns the key, then they actually have
all the "root" privileges as well, since they can create and enter a new user
namespace where they are "root"?  That may not be what people expect.

> +			case KEY_ACE_SYS_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
> +					allow |= ace->mask;
> +				break;

Also known as capable(CAP_SYS_ADMIN).  But also, why does it handle namespaces
differently from KEY_ACE_ROOT?

> +			case KEY_ACE_NET_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
> +					allow |= ace->mask;
> +				break;
> +			}
> +			break;

capable(CAP_NET_ADMIN), and see above.

> +	if (!(allow & desired_perm))
> +		goto no_access;

This is wrong because 'desired_perm' may contain multiple permissions --- and if
*any* are denied, then the permission check should fail.  It should be:

	if (desired_perm & ~allow)
		goto no_access;

> +unsigned int key_acl_to_perm(const struct key_acl *acl)
> +{
> +	unsigned int perm = 0;
> +	int i;
> +
> +	if (!acl || acl->nr_ace = 0)
> +		return 0;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		unsigned int mask = ace->mask & KEY_ACE__PERMS;
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				perm |= (mask & 0x1f) << 24;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_POS_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_POS_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
> +					perm |= KEY_POS_WRITE;
> +				break;

The handling for REVOKE is weird, since it makes it possible that by *adding*
permissions, it can appear that WRITE permission was removed.

Also, why isn't JOIN handled here?

> +			case KEY_ACE_OWNER:
> +				perm |= (mask & 0x1f) << 16;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_USR_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_USR_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
> +					perm |= KEY_USR_WRITE;
> +				break;
> +			case KEY_ACE_GROUP:
> +				perm |= (mask & 0x1f) << 8;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_GRP_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_GRP_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
> +					perm |= KEY_GRP_WRITE;
> +				break;
> +			case KEY_ACE_EVERYONE:
> +				perm |= (mask & 0x1f);
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_OTH_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_OTH_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
> +					perm |= KEY_OTH_WRITE;
> +				break;
> +			}

Again, ignoring the "non-special" ACEs will cause the returned permissions mask
to be an under-estimate rather than an over-estimate.  I'm not sure that's a
good idea.

> +static void key_acl_rcu(struct rcu_head *rcu)
> +{
> +	kfree(container_of(rcu, struct key_acl, rcu));
> +}
> +
> +/*
> + * Destroy a key's ACL.
> + */
> +void key_put_acl(struct key_acl *acl)
> +{
> +	if (refcount_dec_and_test(&acl->usage))
> +		call_rcu(&acl->rcu, key_acl_rcu);
> +}

Use kfree_rcu().

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	check_pos = false;
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		if (ace->special_id = KEY_ACE_POSSESSOR) {
> +			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) =
> +			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
> +				check_pos = true;
> +			break;
> +		}
> +	}

Bug: this will break from the loop if it encounters an ACE for uid or gid 4 (the
value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL before
special_id = KEY_ACE_POSSESSOR can be considered meaningful.

> +static struct key_acl tp_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

It's not obvious what 'tp' means.  How about:

static struct key_acl thread_keyring_acl = {
	...
};

#define process_keyring_acl thread_keyring_acl

> +
> +static struct key_acl session_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};
...
>  
>  		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
> -					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
> -					flags, NULL, NULL);
> +					&session_keyring_acl, flags, NULL, NULL);
>  		if (IS_ERR(keyring))
>  			return PTR_ERR(keyring);

Why give JOIN permission to anonymous session keyrings? They shouldn't be
joinable --- and they weren't prior to this patch, since they were *not* given
KEY_USR_SEARCH permission.

> @@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
>  	if (PTR_ERR(keyring) = -ENOKEY) {
>  		/* not found - try and create a new one */
>  		keyring = keyring_alloc(
> -			name, old->uid, old->gid, old,
> -			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
> +			name, old->uid, old->gid, old, &joinable_keyring_acl,
>  			KEY_ALLOC_IN_QUOTA, NULL, NULL);
>  		if (IS_ERR(keyring)) {
>  			ret = PTR_ERR(keyring);
> @@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
>  		goto error3;
>  	}

This is also a behavior change which needs to be explained and justified on its
own.  A named keyring created with keyctl_join_session_keyring(name) was not
previously joinable by default, but after this patch it is.

>  
> +	ret = key_task_permission(make_key_ref(keyring, false), old,
> +				  KEY_NEED_JOIN);
> +	if (ret < 0)
> +		goto error3;
> +

find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
JOIN as well.  Is it intentional that two different permissions are being
checked?

> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index f5d304736852..7c06837802be 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
>  {
>  	struct key *key;
>  	struct key_security_struct *ksec;
> +	unsigned oldstyle_perm;
>  	u32 sid;
>  
>  	/* if no specific permissions are requested, we skip the
> @@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
>  	if (perm = 0)
>  		return 0;
>  
> +	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
> +				KEY_NEED_SEARCH | KEY_NEED_LINK);
> +	if (perm & KEY_NEED_INVAL)
> +		oldstyle_perm |= KEY_NEED_SEARCH;

Isn't this going to need to be SETATTR rather than SEARCH?  Even if we can't
update SELinux to be aware of the new-style permissions we still need to fix the
"search also means delete" bug.  Otherwise it will remain impossible to use
SELinux to enforce a read-only view of a key hierarchy.

> +	if (perm & KEY_NEED_REVOKE)
> +		oldstyle_perm |= KEY_NEED_WRITE;

Another breaking change which needs to be explained and justified.  Before
either the write or setattr SELinux key permissions was sufficient for
revocation, but now only write is.

> +	if (perm & KEY_NEED_SETSEC)
> +		oldstyle_perm |= 0x20;

Magic number.

> +	if (perm & KEY_NEED_JOIN)
> +		oldstyle_perm |= KEY_NEED_LINK;

The old-style permission for joining keyrings was SEARCH, not LINK.  Probably it
*should* have been LINK, but it's still a breaking change which needs to be
justified on its own...

> @@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
>  #endif
>  	if (perm & KEY_NEED_READ)
>  		request = MAY_READ;
> -	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
> +	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
> +		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
>  		request = MAY_WRITE;
>  	rc = smk_access(tkp, keyp->security, request, &ad);
>  	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

Why does Smack ignore requests for SEARCH permission?

Eric

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-10-02  2:59     ` Eric Biggers
  0 siblings, 0 replies; 15+ messages in thread
From: Eric Biggers @ 2017-10-02  2:59 UTC (permalink / raw)
  To: David Howells; +Cc: Eric Biggers, keyrings, linux-security-module, linux-kernel

Hi David,

On Wed, Sep 27, 2017 at 12:41:41PM +0100, David Howells wrote:
>     
>     Replace the uid/gid/perm permissions checking on a key with an ACL to allow
>     the SETATTR permission to be split.  The problem is that SETATTR covers a
>     slew of things, not all of which should be grouped together.  This
>     includes:
>     
>      (1) Changing the key ownership.
>     
>      (2) Changing the security information.
>     
>      (3) Keyring restriction.
>     
>      (4) Expiry time.
>     
>      (5) Revocation.
>     
>     and it has also been proposed to add:
>     
>      (6) Invalidation.
>     
>     The above can be divided into three groups: Controlling access (1), (2) and
>     (3), managing the content at construction time (4) and managing the key (5)
>     and (6).

This is interesting work, though it adds complexity and makes a lot of subtle
(and potentially breaking) changes to which permissions are required for various
things.  First I think you need to start out with a better statement of the
problems you are trying to solve.  The patch does much more than simply split up
the SETATTR permission --- for example, it also adds the ability to assign
permissions to specific uids, gids, and capabilities.  Who is planning to use
those features and why?

>     The KEYCTL_SETATTR function is then deprecated.  If called, it will

KEYCTL_SETPERM

>     construct an ACL to reflect the mask it is given, using possessor, owner,
>     group and other ACE's as appropriate if any of those elements are granted
>     any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
>     SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
>     CLEAR.  JOIN is turned on if a keyring is being altered.

The proposed changes to keyctl_setperm_key() actually never enable INVAL at all,
which doesn't match the description here.  Also, all breaking changes need to be
justified.  If keyctl_setperm(key, KEY_*_SEARCH) is no longer going to allow the
key to be invalidated (as I had proposed earlier), that is really its own change
which needs its own justification; it shouldn't be hidden in a larger patch.

>     will return an error if SETACL has been called on a key.

That is simplest, but it doesn't match the behavior of POSIX ACLs, for example.
With POSIX ACLs you can still chmod() a file that has an ACL.

>     The KEYCTL_DESCRIBE function then creates a permissions mask to return
>     depending on possessor, owner, group and other ACEs, indicating SETATTR if
>     any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
>     of WRITE, REVOKE or CLEAR are set.

Ignoring ACEs for specific users, groups, and capabilities may be problematic
because the returned mask will under-estimate rather than over-estimate the
permissions that have been granted.  With POSIX ACLs, for example, the union of
all permissions that have been granted to any subjects other than the regular
ones is reflected in the group entry.  I believe that's generally considered
better from a security perspective, because then no permissions are "hidden"
from a listing of the regular (non-ACL) permissions only.

>     Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
>     the value set with KEYCTL_SETATTR - but this is already true because keys
>     that lack ->read() can't have READ set and keys that lack ->write() can't
>     have WRITE set.

Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
that lacks ->update().  They are only omitted from the default permissions.

>     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
>     set, or if the caller has a valid instantiation auth token.

This doesn't match the code, which asks for WRITE permission only.  It's also a
breaking change which needs to be justified on its own.  Also I'm not sure that
WRITE permission actually makes sense, given that KEYCTL_SET_TIMEOUT doesn't
modify the payload of the key.

> +static struct key_acl blacklist_key_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

Designators into flexible arrays are a gcc extension which doesn't work with
clang.  Use this instead:

	.aces = {
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
			.special_id = KEY_ACE_POSSESSOR,
		},
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
			.special_id = KEY_ACE_OWNER,
		},
	},

It's also difficult to read these lists of ACEs.  An ACE should read as "who",
then "what".  But now it reads as "part of who", then "what", then "the rest of
who".  It may be helpful to define some macros to create the entries more
concisely:

	.aces = {
		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
	}

> diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
> new file mode 100644
> index 000000000000..7a738569a673
> --- /dev/null
> +++ b/include/linux/keyctl.h
> @@ -0,0 +1,3 @@
> +#define key_ace user_key_ace
> +#include <uapi/linux/keyctl.h>
> +#undef key_ace

This is ugly.  How about using kernel_key_ace for the internal kernel version?

> +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */

What is a "specific subject"?

> +#define KEY_ACE_ROOT		5	/* The user namespace root user */

Which user namespace?

> +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */

In what user namespace?

> +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */

The implementations of these don't seem to be included in the patch.

> @@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
>  	struct key *keyring;
>  	long ret;
>  
> -	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
> +	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
>  	if (IS_ERR(keyring_ref)) {
>  		ret = PTR_ERR(keyring_ref);
> -
> -		/* Root is permitted to invalidate certain special keyrings */
> -		if (capable(CAP_SYS_ADMIN)) {
> -			keyring_ref = lookup_user_key(ringid, 0, 0);
> -			if (IS_ERR(keyring_ref))
> -				goto error;
> -			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
> -				     &key_ref_to_ptr(keyring_ref)->flags))
> -				goto clear;
> -			goto error_put;
> -		}
> -
>  		goto error;
>  	}

The CLEAR permission is weird because WRITE is a superset of it.  (Clearing a
keyring is equivalent to unlinking all keys in it.)  Permissions should be
orthogonal.  Did you consider instead having one permission for adding links to
a keyring and one for deleting links?  I'm thinking about use cases similar to
that which the sticky bit on files is used for...

> +	for (i = 0; i < 4; i++) {
> +		struct key_ace *ace = &acl->aces[j];
> +		unsigned int subset = (perm >> (i * 8)) & 0x3f;
> +
> +		if (!subset)
> +			continue;
> +		ace->special_id = KEY_ACE_EVERYONE + i;
> +		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);

No magic numbers please.  What are 0x3f and 0x1f?

Also, this code is assuming that the subject values are numbered in a certain
way.  There should be some BUILD_BUG_ON()s to verify that.

> +		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
> +			ace->mask |= KEY_ACE_REVOKE;
> +		if (subset & KEY_OTH_SETATTR)
> +			ace->mask |= KEY_ACE_SET_SECURITY;
> +		if (key->type == &key_type_keyring) {
> +			ace->mask |= KEY_ACE_JOIN;

Why is JOIN permission always given to keyrings?  If someone has JOIN permission
(or LINK permission, for that matter) on a keyring than they can acquire the
possessor permissions.  So always giving JOIN defeats the point of having all
the different non-possessor permissions...

> +			if (subset & KEY_OTH_WRITE)
> +				ace->mask |= KEY_ACE_CLEAR;
> +		}

Granting INVAL permission is missing.

> @@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
>  	long ret;
>  
>  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> -				  KEY_NEED_SETATTR);
> +				  KEY_NEED_WRITE);

As mentioned earlier, this is a breaking change which needs to be justified on
its own.

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	if (!acl || acl->nr_ace == 0)
> +		goto no_access_rcu;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				if (is_key_possessed(key_ref))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_OWNER:
> +				if (uid_eq(key->uid, cred->fsuid))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_GROUP:
> +				if (gid_valid(key->gid) &&
> +				    gid_eq(key->gid, cred->fsgid))
> +					allow |= ace->mask;
> +				else if (groups_search(cred->group_info, key->gid))
> +					allow |= ace->mask;

Why call groups_search() when the key gid isn't valid?

> +			case KEY_ACE_ROOT:
> +				if (uid_eq(key->uid, cred->user_ns->owner))
> +					allow |= ace->mask;
> +				break;

Not sure I understand this.  So if someone owns the key, then they actually have
all the "root" privileges as well, since they can create and enter a new user
namespace where they are "root"?  That may not be what people expect.

> +			case KEY_ACE_SYS_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
> +					allow |= ace->mask;
> +				break;

Also known as capable(CAP_SYS_ADMIN).  But also, why does it handle namespaces
differently from KEY_ACE_ROOT?

> +			case KEY_ACE_NET_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
> +					allow |= ace->mask;
> +				break;
> +			}
> +			break;

capable(CAP_NET_ADMIN), and see above.

> +	if (!(allow & desired_perm))
> +		goto no_access;

This is wrong because 'desired_perm' may contain multiple permissions --- and if
*any* are denied, then the permission check should fail.  It should be:

	if (desired_perm & ~allow)
		goto no_access;

> +unsigned int key_acl_to_perm(const struct key_acl *acl)
> +{
> +	unsigned int perm = 0;
> +	int i;
> +
> +	if (!acl || acl->nr_ace == 0)
> +		return 0;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		unsigned int mask = ace->mask & KEY_ACE__PERMS;
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				perm |= (mask & 0x1f) << 24;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_POS_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_POS_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
> +					perm |= KEY_POS_WRITE;
> +				break;

The handling for REVOKE is weird, since it makes it possible that by *adding*
permissions, it can appear that WRITE permission was removed.

Also, why isn't JOIN handled here?

> +			case KEY_ACE_OWNER:
> +				perm |= (mask & 0x1f) << 16;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_USR_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_USR_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
> +					perm |= KEY_USR_WRITE;
> +				break;
> +			case KEY_ACE_GROUP:
> +				perm |= (mask & 0x1f) << 8;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_GRP_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_GRP_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
> +					perm |= KEY_GRP_WRITE;
> +				break;
> +			case KEY_ACE_EVERYONE:
> +				perm |= (mask & 0x1f);
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_OTH_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_OTH_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
> +					perm |= KEY_OTH_WRITE;
> +				break;
> +			}

Again, ignoring the "non-special" ACEs will cause the returned permissions mask
to be an under-estimate rather than an over-estimate.  I'm not sure that's a
good idea.

> +static void key_acl_rcu(struct rcu_head *rcu)
> +{
> +	kfree(container_of(rcu, struct key_acl, rcu));
> +}
> +
> +/*
> + * Destroy a key's ACL.
> + */
> +void key_put_acl(struct key_acl *acl)
> +{
> +	if (refcount_dec_and_test(&acl->usage))
> +		call_rcu(&acl->rcu, key_acl_rcu);
> +}

Use kfree_rcu().

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	check_pos = false;
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		if (ace->special_id == KEY_ACE_POSSESSOR) {
> +			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
> +			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
> +				check_pos = true;
> +			break;
> +		}
> +	}

Bug: this will break from the loop if it encounters an ACE for uid or gid 4 (the
value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL before
special_id == KEY_ACE_POSSESSOR can be considered meaningful.

> +static struct key_acl tp_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

It's not obvious what 'tp' means.  How about:

static struct key_acl thread_keyring_acl = {
	...
};

#define process_keyring_acl thread_keyring_acl

> +
> +static struct key_acl session_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};
...
>  
>  		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
> -					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
> -					flags, NULL, NULL);
> +					&session_keyring_acl, flags, NULL, NULL);
>  		if (IS_ERR(keyring))
>  			return PTR_ERR(keyring);

Why give JOIN permission to anonymous session keyrings? They shouldn't be
joinable --- and they weren't prior to this patch, since they were *not* given
KEY_USR_SEARCH permission.

> @@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
>  	if (PTR_ERR(keyring) == -ENOKEY) {
>  		/* not found - try and create a new one */
>  		keyring = keyring_alloc(
> -			name, old->uid, old->gid, old,
> -			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
> +			name, old->uid, old->gid, old, &joinable_keyring_acl,
>  			KEY_ALLOC_IN_QUOTA, NULL, NULL);
>  		if (IS_ERR(keyring)) {
>  			ret = PTR_ERR(keyring);
> @@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
>  		goto error3;
>  	}

This is also a behavior change which needs to be explained and justified on its
own.  A named keyring created with keyctl_join_session_keyring(name) was not
previously joinable by default, but after this patch it is.

>  
> +	ret = key_task_permission(make_key_ref(keyring, false), old,
> +				  KEY_NEED_JOIN);
> +	if (ret < 0)
> +		goto error3;
> +

find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
JOIN as well.  Is it intentional that two different permissions are being
checked?

> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index f5d304736852..7c06837802be 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
>  {
>  	struct key *key;
>  	struct key_security_struct *ksec;
> +	unsigned oldstyle_perm;
>  	u32 sid;
>  
>  	/* if no specific permissions are requested, we skip the
> @@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
>  	if (perm == 0)
>  		return 0;
>  
> +	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
> +				KEY_NEED_SEARCH | KEY_NEED_LINK);
> +	if (perm & KEY_NEED_INVAL)
> +		oldstyle_perm |= KEY_NEED_SEARCH;

Isn't this going to need to be SETATTR rather than SEARCH?  Even if we can't
update SELinux to be aware of the new-style permissions we still need to fix the
"search also means delete" bug.  Otherwise it will remain impossible to use
SELinux to enforce a read-only view of a key hierarchy.

> +	if (perm & KEY_NEED_REVOKE)
> +		oldstyle_perm |= KEY_NEED_WRITE;

Another breaking change which needs to be explained and justified.  Before
either the write or setattr SELinux key permissions was sufficient for
revocation, but now only write is.

> +	if (perm & KEY_NEED_SETSEC)
> +		oldstyle_perm |= 0x20;

Magic number.

> +	if (perm & KEY_NEED_JOIN)
> +		oldstyle_perm |= KEY_NEED_LINK;

The old-style permission for joining keyrings was SEARCH, not LINK.  Probably it
*should* have been LINK, but it's still a breaking change which needs to be
justified on its own...

> @@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
>  #endif
>  	if (perm & KEY_NEED_READ)
>  		request = MAY_READ;
> -	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
> +	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
> +		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
>  		request = MAY_WRITE;
>  	rc = smk_access(tkp, keyp->security, request, &ad);
>  	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

Why does Smack ignore requests for SEARCH permission?

Eric

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-10-02  2:59     ` Eric Biggers
  0 siblings, 0 replies; 15+ messages in thread
From: Eric Biggers @ 2017-10-02  2:59 UTC (permalink / raw)
  To: linux-security-module

Hi David,

On Wed, Sep 27, 2017 at 12:41:41PM +0100, David Howells wrote:
>     
>     Replace the uid/gid/perm permissions checking on a key with an ACL to allow
>     the SETATTR permission to be split.  The problem is that SETATTR covers a
>     slew of things, not all of which should be grouped together.  This
>     includes:
>     
>      (1) Changing the key ownership.
>     
>      (2) Changing the security information.
>     
>      (3) Keyring restriction.
>     
>      (4) Expiry time.
>     
>      (5) Revocation.
>     
>     and it has also been proposed to add:
>     
>      (6) Invalidation.
>     
>     The above can be divided into three groups: Controlling access (1), (2) and
>     (3), managing the content at construction time (4) and managing the key (5)
>     and (6).

This is interesting work, though it adds complexity and makes a lot of subtle
(and potentially breaking) changes to which permissions are required for various
things.  First I think you need to start out with a better statement of the
problems you are trying to solve.  The patch does much more than simply split up
the SETATTR permission --- for example, it also adds the ability to assign
permissions to specific uids, gids, and capabilities.  Who is planning to use
those features and why?

>     The KEYCTL_SETATTR function is then deprecated.  If called, it will

KEYCTL_SETPERM

>     construct an ACL to reflect the mask it is given, using possessor, owner,
>     group and other ACE's as appropriate if any of those elements are granted
>     any permissions.  SETATTR permission turns on all of INVAL, REVOKE and
>     SET_SECURITY.  WRITE permission turns on WRITE, REVOKE and, if a keyring,
>     CLEAR.  JOIN is turned on if a keyring is being altered.

The proposed changes to keyctl_setperm_key() actually never enable INVAL at all,
which doesn't match the description here.  Also, all breaking changes need to be
justified.  If keyctl_setperm(key, KEY_*_SEARCH) is no longer going to allow the
key to be invalidated (as I had proposed earlier), that is really its own change
which needs its own justification; it shouldn't be hidden in a larger patch.

>     will return an error if SETACL has been called on a key.

That is simplest, but it doesn't match the behavior of POSIX ACLs, for example.
With POSIX ACLs you can still chmod() a file that has an ACL.

>     The KEYCTL_DESCRIBE function then creates a permissions mask to return
>     depending on possessor, owner, group and other ACEs, indicating SETATTR if
>     any of INVAL, REVOKE and SET_SECURITY are set and indicating WRITE if any
>     of WRITE, REVOKE or CLEAR are set.

Ignoring ACEs for specific users, groups, and capabilities may be problematic
because the returned mask will under-estimate rather than over-estimate the
permissions that have been granted.  With POSIX ACLs, for example, the union of
all permissions that have been granted to any subjects other than the regular
ones is reflected in the group entry.  I believe that's generally considered
better from a security perspective, because then no permissions are "hidden"
from a listing of the regular (non-ACL) permissions only.

>     Note that the value subsequently returned by KEYCTL_DESCRIBE may not match
>     the value set with KEYCTL_SETATTR - but this is already true because keys
>     that lack ->read() can't have READ set and keys that lack ->write() can't
>     have WRITE set.

Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
that lacks ->update().  They are only omitted from the default permissions.

>     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
>     set, or if the caller has a valid instantiation auth token.

This doesn't match the code, which asks for WRITE permission only.  It's also a
breaking change which needs to be justified on its own.  Also I'm not sure that
WRITE permission actually makes sense, given that KEYCTL_SET_TIMEOUT doesn't
modify the payload of the key.

> +static struct key_acl blacklist_key_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

Designators into flexible arrays are a gcc extension which doesn't work with
clang.  Use this instead:

	.aces = {
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_SEARCH | KEY_ACE_READ),
			.special_id = KEY_ACE_POSSESSOR,
		},
		{
			.mask = KEY_ACE_SPECIAL | (KEY_ACE_VIEW | KEY_ACE_SEARCH),
			.special_id = KEY_ACE_OWNER,
		},
	},

It's also difficult to read these lists of ACEs.  An ACE should read as "who",
then "what".  But now it reads as "part of who", then "what", then "the rest of
who".  It may be helpful to define some macros to create the entries more
concisely:

	.aces = {
		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
	}

> diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
> new file mode 100644
> index 000000000000..7a738569a673
> --- /dev/null
> +++ b/include/linux/keyctl.h
> @@ -0,0 +1,3 @@
> +#define key_ace user_key_ace
> +#include <uapi/linux/keyctl.h>
> +#undef key_ace

This is ugly.  How about using kernel_key_ace for the internal kernel version?

> +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */

What is a "specific subject"?

> +#define KEY_ACE_ROOT		5	/* The user namespace root user */

Which user namespace?

> +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */

In what user namespace?

> +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */

The implementations of these don't seem to be included in the patch.

> @@ -461,31 +441,17 @@ long keyctl_keyring_clear(key_serial_t ringid)
>  	struct key *keyring;
>  	long ret;
>  
> -	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
> +	keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR);
>  	if (IS_ERR(keyring_ref)) {
>  		ret = PTR_ERR(keyring_ref);
> -
> -		/* Root is permitted to invalidate certain special keyrings */
> -		if (capable(CAP_SYS_ADMIN)) {
> -			keyring_ref = lookup_user_key(ringid, 0, 0);
> -			if (IS_ERR(keyring_ref))
> -				goto error;
> -			if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
> -				     &key_ref_to_ptr(keyring_ref)->flags))
> -				goto clear;
> -			goto error_put;
> -		}
> -
>  		goto error;
>  	}

The CLEAR permission is weird because WRITE is a superset of it.  (Clearing a
keyring is equivalent to unlinking all keys in it.)  Permissions should be
orthogonal.  Did you consider instead having one permission for adding links to
a keyring and one for deleting links?  I'm thinking about use cases similar to
that which the sticky bit on files is used for...

> +	for (i = 0; i < 4; i++) {
> +		struct key_ace *ace = &acl->aces[j];
> +		unsigned int subset = (perm >> (i * 8)) & 0x3f;
> +
> +		if (!subset)
> +			continue;
> +		ace->special_id = KEY_ACE_EVERYONE + i;
> +		ace->mask = KEY_ACE_SPECIAL | (subset & 0x1f);

No magic numbers please.  What are 0x3f and 0x1f?

Also, this code is assuming that the subject values are numbered in a certain
way.  There should be some BUILD_BUG_ON()s to verify that.

> +		if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
> +			ace->mask |= KEY_ACE_REVOKE;
> +		if (subset & KEY_OTH_SETATTR)
> +			ace->mask |= KEY_ACE_SET_SECURITY;
> +		if (key->type == &key_type_keyring) {
> +			ace->mask |= KEY_ACE_JOIN;

Why is JOIN permission always given to keyrings?  If someone has JOIN permission
(or LINK permission, for that matter) on a keyring than they can acquire the
possessor permissions.  So always giving JOIN defeats the point of having all
the different non-possessor permissions...

> +			if (subset & KEY_OTH_WRITE)
> +				ace->mask |= KEY_ACE_CLEAR;
> +		}

Granting INVAL permission is missing.

> @@ -1333,7 +1349,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
>  	long ret;
>  
>  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> -				  KEY_NEED_SETATTR);
> +				  KEY_NEED_WRITE);

As mentioned earlier, this is a breaking change which needs to be justified on
its own.

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	if (!acl || acl->nr_ace == 0)
> +		goto no_access_rcu;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				if (is_key_possessed(key_ref))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_OWNER:
> +				if (uid_eq(key->uid, cred->fsuid))
> +					allow |= ace->mask;
> +				break;
> +			case KEY_ACE_GROUP:
> +				if (gid_valid(key->gid) &&
> +				    gid_eq(key->gid, cred->fsgid))
> +					allow |= ace->mask;
> +				else if (groups_search(cred->group_info, key->gid))
> +					allow |= ace->mask;

Why call groups_search() when the key gid isn't valid?

> +			case KEY_ACE_ROOT:
> +				if (uid_eq(key->uid, cred->user_ns->owner))
> +					allow |= ace->mask;
> +				break;

Not sure I understand this.  So if someone owns the key, then they actually have
all the "root" privileges as well, since they can create and enter a new user
namespace where they are "root"?  That may not be what people expect.

> +			case KEY_ACE_SYS_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_SYS_ADMIN))
> +					allow |= ace->mask;
> +				break;

Also known as capable(CAP_SYS_ADMIN).  But also, why does it handle namespaces
differently from KEY_ACE_ROOT?

> +			case KEY_ACE_NET_ADMIN:
> +				if (ns_capable(&init_user_ns, CAP_NET_ADMIN))
> +					allow |= ace->mask;
> +				break;
> +			}
> +			break;

capable(CAP_NET_ADMIN), and see above.

> +	if (!(allow & desired_perm))
> +		goto no_access;

This is wrong because 'desired_perm' may contain multiple permissions --- and if
*any* are denied, then the permission check should fail.  It should be:

	if (desired_perm & ~allow)
		goto no_access;

> +unsigned int key_acl_to_perm(const struct key_acl *acl)
> +{
> +	unsigned int perm = 0;
> +	int i;
> +
> +	if (!acl || acl->nr_ace == 0)
> +		return 0;
> +
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		unsigned int mask = ace->mask & KEY_ACE__PERMS;
> +
> +		switch (ace->mask & KEY_ACE__IDENTITY) {
> +		case KEY_ACE_SPECIAL:
> +			switch (ace->special_id) {
> +			case KEY_ACE_POSSESSOR:
> +				perm |= (mask & 0x1f) << 24;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_POS_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_POS_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_POS_SETATTR))
> +					perm |= KEY_POS_WRITE;
> +				break;

The handling for REVOKE is weird, since it makes it possible that by *adding*
permissions, it can appear that WRITE permission was removed.

Also, why isn't JOIN handled here?

> +			case KEY_ACE_OWNER:
> +				perm |= (mask & 0x1f) << 16;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_USR_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_USR_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_USR_SETATTR))
> +					perm |= KEY_USR_WRITE;
> +				break;
> +			case KEY_ACE_GROUP:
> +				perm |= (mask & 0x1f) << 8;
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_GRP_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_GRP_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_GRP_SETATTR))
> +					perm |= KEY_GRP_WRITE;
> +				break;
> +			case KEY_ACE_EVERYONE:
> +				perm |= (mask & 0x1f);
> +				if (mask & (KEY_ACE_INVAL | KEY_ACE_SET_SECURITY))
> +					perm |= KEY_OTH_SETATTR;
> +				if (mask & KEY_ACE_CLEAR)
> +					perm |= KEY_OTH_WRITE;
> +				if ((mask & KEY_ACE_REVOKE) && !(perm & KEY_OTH_SETATTR))
> +					perm |= KEY_OTH_WRITE;
> +				break;
> +			}

Again, ignoring the "non-special" ACEs will cause the returned permissions mask
to be an under-estimate rather than an over-estimate.  I'm not sure that's a
good idea.

> +static void key_acl_rcu(struct rcu_head *rcu)
> +{
> +	kfree(container_of(rcu, struct key_acl, rcu));
> +}
> +
> +/*
> + * Destroy a key's ACL.
> + */
> +void key_put_acl(struct key_acl *acl)
> +{
> +	if (refcount_dec_and_test(&acl->usage))
> +		call_rcu(&acl->rcu, key_acl_rcu);
> +}

Use kfree_rcu().

> +	rcu_read_lock();
> +
> +	acl = rcu_dereference(key->acl);
> +	check_pos = false;
> +	for (i = 0; i < acl->nr_ace; i++) {
> +		const struct key_ace *ace = &acl->aces[i];
> +		if (ace->special_id == KEY_ACE_POSSESSOR) {
> +			if ((ace->mask & (KEY_ACE__IDENTITY | KEY_ACE_VIEW)) ==
> +			    (KEY_ACE_SPECIAL | KEY_ACE_VIEW))
> +				check_pos = true;
> +			break;
> +		}
> +	}

Bug: this will break from the loop if it encounters an ACE for uid or gid 4 (the
value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL before
special_id == KEY_ACE_POSSESSOR can be considered meaningful.

> +static struct key_acl tp_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};

It's not obvious what 'tp' means.  How about:

static struct key_acl thread_keyring_acl = {
	...
};

#define process_keyring_acl thread_keyring_acl

> +
> +static struct key_acl session_keyring_acl = {
> +	.usage	= REFCOUNT_INIT(1),
> +	.nr_ace	= 2,
> +	.aces[0] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE__PERMS,
> +		.special_id = KEY_ACE_POSSESSOR,
> +	},
> +	.aces[1] = {
> +		.mask = KEY_ACE_SPECIAL | KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_JOIN,
> +		.special_id = KEY_ACE_OWNER,
> +	},
> +};
...
>  
>  		keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
> -					KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
> -					flags, NULL, NULL);
> +					&session_keyring_acl, flags, NULL, NULL);
>  		if (IS_ERR(keyring))
>  			return PTR_ERR(keyring);

Why give JOIN permission to anonymous session keyrings? They shouldn't be
joinable --- and they weren't prior to this patch, since they were *not* given
KEY_USR_SEARCH permission.

> @@ -800,8 +836,7 @@ long join_session_keyring(const char *name)
>  	if (PTR_ERR(keyring) == -ENOKEY) {
>  		/* not found - try and create a new one */
>  		keyring = keyring_alloc(
> -			name, old->uid, old->gid, old,
> -			KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
> +			name, old->uid, old->gid, old, &joinable_keyring_acl,
>  			KEY_ALLOC_IN_QUOTA, NULL, NULL);
>  		if (IS_ERR(keyring)) {
>  			ret = PTR_ERR(keyring);
> @@ -815,6 +850,11 @@ long join_session_keyring(const char *name)
>  		goto error3;
>  	}

This is also a behavior change which needs to be explained and justified on its
own.  A named keyring created with keyctl_join_session_keyring(name) was not
previously joinable by default, but after this patch it is.

>  
> +	ret = key_task_permission(make_key_ref(keyring, false), old,
> +				  KEY_NEED_JOIN);
> +	if (ret < 0)
> +		goto error3;
> +

find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
JOIN as well.  Is it intentional that two different permissions are being
checked?

> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index f5d304736852..7c06837802be 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6157,6 +6157,7 @@ static int selinux_key_permission(key_ref_t key_ref,
>  {
>  	struct key *key;
>  	struct key_security_struct *ksec;
> +	unsigned oldstyle_perm;
>  	u32 sid;
>  
>  	/* if no specific permissions are requested, we skip the
> @@ -6165,12 +6166,25 @@ static int selinux_key_permission(key_ref_t key_ref,
>  	if (perm == 0)
>  		return 0;
>  
> +	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE |
> +				KEY_NEED_SEARCH | KEY_NEED_LINK);
> +	if (perm & KEY_NEED_INVAL)
> +		oldstyle_perm |= KEY_NEED_SEARCH;

Isn't this going to need to be SETATTR rather than SEARCH?  Even if we can't
update SELinux to be aware of the new-style permissions we still need to fix the
"search also means delete" bug.  Otherwise it will remain impossible to use
SELinux to enforce a read-only view of a key hierarchy.

> +	if (perm & KEY_NEED_REVOKE)
> +		oldstyle_perm |= KEY_NEED_WRITE;

Another breaking change which needs to be explained and justified.  Before
either the write or setattr SELinux key permissions was sufficient for
revocation, but now only write is.

> +	if (perm & KEY_NEED_SETSEC)
> +		oldstyle_perm |= 0x20;

Magic number.

> +	if (perm & KEY_NEED_JOIN)
> +		oldstyle_perm |= KEY_NEED_LINK;

The old-style permission for joining keyrings was SEARCH, not LINK.  Probably it
*should* have been LINK, but it's still a breaking change which needs to be
justified on its own...

> @@ -4377,7 +4377,8 @@ static int smack_key_permission(key_ref_t key_ref,
>  #endif
>  	if (perm & KEY_NEED_READ)
>  		request = MAY_READ;
> -	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR))
> +	if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC |
> +		    KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR))
>  		request = MAY_WRITE;
>  	rc = smk_access(tkp, keyp->security, request, &ad);
>  	rc = smk_bu_note("key access", tkp, keyp->security, request, rc);

Why does Smack ignore requests for SEARCH permission?

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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
  2017-09-27 11:41   ` David Howells
  (?)
@ 2017-10-02 23:35     ` David Howells
  -1 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-10-02 23:35 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> This is interesting work, though it adds complexity and makes a lot of
> subtle (and potentially breaking) changes to which permissions are required
> for various things.  First I think you need to start out with a better
> statement of the problems you are trying to solve.  The patch does much more
> than simply split up the SETATTR permission --- for example, it also adds
> the ability to assign permissions to specific uids, gids, and capabilities.
> Who is planning to use those features and why?

I should split out the additional subject types (uid, gid, cap-based) into a
separate patch, but having them in one patch helped design it.

> The proposed changes to keyctl_setperm_key() actually never enable INVAL at
> all

Fixed.  Now done if KEY_xxx_SEARCH is given.

> >     will return an error if SETACL has been called on a key.
> 
> That is simplest, but it doesn't match the behavior of POSIX ACLs, for
> example.  With POSIX ACLs you can still chmod() a file that has an ACL.

Does chmod() remove a POSIX ACL from a file?

> >     The KEYCTL_DESCRIBE function then creates a permissions mask to return
> >     depending on possessor, owner, group and other ACEs, indicating
> >     SETATTR if any of INVAL, REVOKE and SET_SECURITY are set and
> >     indicating WRITE if any of WRITE, REVOKE or CLEAR are set.
> 
> Ignoring ACEs for specific users, groups, and capabilities may be problematic
> because the returned mask will under-estimate rather than over-estimate the
> permissions that have been granted.

You have a point, but KEYCTL_DESCRIBE doesn't return the set of permissions
that has been granted to the caller.  The KEYCTL_DESCRIBE interface can't be
made to accurately describe the ACL.

> With POSIX ACLs, for example, the union of all permissions that have been
> granted to any subjects other than the regular ones

Define 'regular ones'.  For the keys case, do you mean possessor, user, group
and other?

> is reflected in the group entry.  I believe that's generally considered
> better from a security perspective, because then no permissions are "hidden"
> from a listing of the regular (non-ACL) permissions only.

I'm not sure how that helps.  If there's a group ID set, then displaying extra
permissions for it because, say, there's a matching UID-specific ACE present
would be giving a false picture of the permissions set.


> >     Note that the value subsequently returned by KEYCTL_DESCRIBE may not
> >     match the value set with KEYCTL_SETATTR - but this is already true
> >     because keys that lack ->read() can't have READ set and keys that lack
> >     ->write() can't have WRITE set.
> 
> Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
> that lacks ->update().  They are only omitted from the default permissions.

You are right.  This should be fixed, assuming we don't move to some other
permissions model.

> >     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
> >     set, or if the caller has a valid instantiation auth token.
> 
> This doesn't match the code, which asks for WRITE permission only.  It's
> also a breaking change which needs to be justified on its own.  Also I'm not
> sure that WRITE permission actually makes sense, given that
> KEYCTL_SET_TIMEOUT doesn't modify the payload of the key.

I'm not sure I agree.  Whilst it doesn't modify the payload of a key, it is
*about* the lifetime of that payload IMO.

However, given that add_key() grants WRITE and SET_SECURITY/SETATTR permission
to the caller (assuming they stick the new key in a keyring they 'possess')
and request_key() grants permission directly to the upcall to set the timeout,
I wonder how much that matters.  It's just that it's not in the same class as
changing the key permissions.

> Designators into flexible arrays are a gcc extension which doesn't work with
> clang.

Fix clang?  gcc is the standard.  ;-)

> It's also difficult to read these lists of ACEs.  An ACE should read as
> "who", then "what".  But now it reads as "part of who", then "what", then
> "the rest of who".

True.  That makes it occupy less space, though.  I could also flip the order
of .mask and .special_id.

One thing I was wondering about is whether I should make the core ACLs R/O and
skip the inc/dec on the refcount if is_kernel_rodata() on the ACL is true.

> 	.aces = {
> 		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
> 		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
> 	}

Yeah, that looks nicer.

> > +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
> 
> What is a "specific subject"?

'Subject' as in the entity performing the action.  'Specific subject' as
specified by .special_id.  I'm not sure 'specific' is the right word, or
'special' for that matter, but it's how I specify 'macro' things like
'possessor' or 'this key's owner'.

Maybe KEY_ACE_FIXED_SUBJECT or KEY_ACE_MACRO_SUBJECT?  I guess KEY_ACE_UID and
KEY_ACE_GID more fit 'specific subject'.

> > +#define KEY_ACE_ROOT		5	/* The user namespace root user */
> 
> Which user namespace?
>
> > +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> > +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
> 
> In what user namespace?

cred->user_ns as per the cred passed to key_task_permission().

Having dicussed these a bit with Eric Biederman, I'm thinking I probably want
to rejig this, perhaps in favour of specifying the owner of a particular user
namespace.  However, I do think, as mentioned above, I need to split this bit
out into a subsequent patch.

> > +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> > +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
> 
> The implementations of these don't seem to be included in the patch.

As I stated.  I do have implementations of these now, which I've attached to
the bottom of this message.

> The CLEAR permission is weird because WRITE is a superset of it.  (Clearing
> a keyring is equivalent to unlinking all keys in it.)  Permissions should be
> orthogonal.

I know, but I want to be able to grant just the ability to clear a keyring to
the admin.

> Did you consider instead having one permission for adding links
> to a keyring and one for deleting links?  I'm thinking about use cases
> similar to that which the sticky bit on files is used for...

Actually, that would've been better - and possibly could've avoided the need
for invalidate - though invalidate is applied to a specific key, not a
keyring.

However, what do you do about key replacement where a link to an old key is
replaced by add_key() with a link to a new key?  Do you have to have both
permits?  Or a third REPLACE permit?

> No magic numbers please.  What are 0x3f and 0x1f?

Things I don't have symbols for.  I would like to avoid using KEY_OTH_xxx when
referring to values that weren't originally 'other'.  I didn't succeed,
though, as I'm sure you noticed, so I could just use combinations of
KEY_OTH_xxx, I guess.

> Also, this code is assuming that the subject values are numbered in a certain
> way.

The UAPI is defined so.

Further, key_task_permission() in current Linus will fail if this is not so.

> > +		if (key->type = &key_type_keyring) {
> > +			ace->mask |= KEY_ACE_JOIN;
> 
> Why is JOIN permission always given to keyrings?

It isn't, except if one calls KEYCTL_SETPERM or if a keyring is created by
join_session_keyring() or as a normal session keyrign.  add_key() creates
keyrings with default_key_acl.

> If someone has JOIN permission (or LINK permission, for that matter) on a
> keyring than they can acquire the possessor permissions.  So always giving
> JOIN defeats the point of having all the different non-possessor
> permissions...

Without this patch, join_session_keyring() doesn't impose any permissions on
joining a keyring, though find_keyring_by_name() requires SEARCH permission on
a named keyring to be able to find it (and still does - though this should
perhaps be switched to JOIN also).

Yes, LINK permission can be used to 'possess' a keyring - but only if you're
granted it.

I can alter KEYCTL_SETPERM to only give KEY_ACE_JOIN if KEY_ACE_SEARCH.

The problem is that I have to supply JOIN to be backwardly compatible, which
makes it arguable that I need to add it to default_key_acl also.

I should probably make it widely available in the main patch and add a
separate patch limiting its availability.

> Granting INVAL permission is missing.

Fixed.  Setting SEARCH sets INVAL in KEYCTL_SETPERM.

> >  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> > -				  KEY_NEED_SETATTR);
> > +				  KEY_NEED_WRITE);
> 
> As mentioned earlier, this is a breaking change which needs to be justified on
> its own.

I can undo this and defer the thought to a later patch.

> Why call groups_search() when the key gid isn't valid?

Fixed.

> This is wrong because 'desired_perm' may contain multiple permissions ---

No, it can't.  I've fixed the comment.

It was never specified what was meant by specifying multiple permissions and I
don't think I ever used that directly.  The nearest I came was to make
multiple calls to key_{,task_}permission() so that it was obvious in the
caller what was meant.

> The handling for REVOKE is weird, since it makes it possible that by *adding*
> permissions, it can appear that WRITE permission was removed.

I know.  But there's no way to render an ACL down to an old-style permissions
mask accurately.  Revocation permission was enabled by having either SETATTR
or WRITE permission, but WRITE permission was disabled under some
circumstances if ->write() didn't exist.

> Also, why isn't JOIN handled here?

What would you represent it as?

Currently, the nearest thing to a JOIN permission is SEARCH, since you need
that to find the keyring by name in the first place; beyond that, there is no
permission needed to join a keyring.

I can make JOIN enable SEARCH, but if you pass it back to KEYCTL_SETPERM,
you're going to enable SEARCH and JOIN, even if you didn't have SEARCH before.

There isn't an easy answer.

> Again, ignoring the "non-special" ACEs will cause the returned permissions
> mask to be an under-estimate rather than an over-estimate.  I'm not sure
> that's a good idea.

There isn't really a viable alternative.  perm isn't what you, the caller,
have available to you.

> > +void key_put_acl(struct key_acl *acl)
> > +{
> > +	if (refcount_dec_and_test(&acl->usage))
> > +		call_rcu(&acl->rcu, key_acl_rcu);
> > +}
> 
> Use kfree_rcu().

Good idea.  I was thinking that could only deal with objects in which the
rcu_head was first, but that doesn't appear to be the case.

> Bug: this will break from the loop if it encounters an ACE for uid or gid 4
> (the value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL
> before special_id = KEY_ACE_POSSESSOR can be considered meaningful.

Fixed.

> It's not obvious what 'tp' means.  How about:
> 
> static struct key_acl thread_keyring_acl = {
> 	...
> };
> 
> #define process_keyring_acl thread_keyring_acl

I dislike that.  I've changed it to:

static struct key_acl thread_and_process_keyring_acl = ...

> Why give JOIN permission to anonymous session keyrings? They shouldn't be
> joinable --- and they weren't prior to this patch, since they were *not* given
> KEY_USR_SEARCH permission.

Point.  Fixed.

> This is also a behavior change which needs to be explained and justified on
> its own.  A named keyring created with keyctl_join_session_keyring(name) was
> not previously joinable by default, but after this patch it is.

Yeah - this should be its own patch.  If you create a named keyring with
KEYCTL_JOIN_SESSION_KEYRING, doing this again in another process with the same
name ought to join the first one if it's owned by you and lets you find it.

> find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
> JOIN as well.  Is it intentional that two different permissions are being
> checked?

As previously mentioned, just JOIN should probably be used.  In effect, SEARCH
is also being split from JOIN.

> > +	if (perm & KEY_NEED_INVAL)
> > +		oldstyle_perm |= KEY_NEED_SEARCH;
> 
> Isn't this going to need to be SETATTR rather than SEARCH?

No.  Current code requires SEARCH (or CAP_SYS_ADMIN +
KEY_FLAG_ROOT_CAN_INVAL), not SETATTR.  It was *your* proposal to make it use
SETATTR instead.

> Even if we can't update SELinux to be aware of the new-style permissions we
> still need to fix the "search also means delete" bug.  Otherwise it will
> remain impossible to use SELinux to enforce a read-only view of a key
> hierarchy.

SELinux will need fixing if you want to be able to do that.  I need to discuss
how to do this with the SELinux people.

> > +	if (perm & KEY_NEED_REVOKE)
> > +		oldstyle_perm |= KEY_NEED_WRITE;
> 
> Another breaking change which needs to be explained and justified.  Before
> either the write or setattr SELinux key permissions was sufficient for
> revocation, but now only write is.

Fixed.

> > +	if (perm & KEY_NEED_SETSEC)
> > +		oldstyle_perm |= 0x20;
> 
> Magic number.

The symbol no longer exists.  I've #defined OLD_KEY_NEED_SETATTR to it in
hooks.c.

> > +	if (perm & KEY_NEED_JOIN)
> > +		oldstyle_perm |= KEY_NEED_LINK;
> 
> The old-style permission for joining keyrings was SEARCH, not LINK.
> Probably it *should* have been LINK, but it's still a breaking change which
> needs to be justified on its own...

Changed to SEARCH.

> Why does Smack ignore requests for SEARCH permission?

No idea.

David

---
diff --git a/security/keys/compat.c b/security/keys/compat.c
index e87c89c0177c..4a6918f68f6b 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -141,6 +141,12 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 		return keyctl_restrict_keyring(arg2, compat_ptr(arg3),
 					       compat_ptr(arg4));
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl(arg2, compat_ptr(arg3), arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl(arg2, compat_ptr(arg3), arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index a699c5937cbe..1c6efdf04445 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -302,6 +302,14 @@ static inline long compat_keyctl_dh_compute(
 #endif
 #endif
 
+extern long keyctl_get_acl(key_serial_t keyid,
+			   struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+extern long keyctl_set_acl(key_serial_t keyid,
+			   const struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+
+
 /*
  * Debugging key validation
  */
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 51fefec80cce..2773461c96ec 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -961,6 +961,8 @@ long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 			ace->mask |= KEY_ACE_REVOKE;
 		if (subset & KEY_OTH_SETATTR)
 			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (subset & KEY_OTH_SEARCH)
+			ace->mask |= KEY_ACE_INVAL;
 		if (key->type = &key_type_keyring) {
 			ace->mask |= KEY_ACE_JOIN;
 			if (subset & KEY_OTH_WRITE)
@@ -1768,6 +1770,16 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 					       (const char __user *) arg3,
 					       (const char __user *) arg4);
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl((key_serial_t)arg2,
+				      (struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl((key_serial_t)arg2,
+				      (const struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 5e186b0f802d..7056ae3e8d8d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/security.h>
 #include <linux/user_namespace.h>
+#include <linux/uaccess.h>
 #include "internal.h"
 
 struct key_acl default_key_acl = {
@@ -243,3 +244,184 @@ void key_put_acl(struct key_acl *acl)
 	if (refcount_dec_and_test(&acl->usage))
 		call_rcu(&acl->rcu, key_acl_rcu);
 }
+
+/*
+ * Get the ACL attached to key.
+ */
+long keyctl_get_acl(key_serial_t keyid,
+		    struct user_key_ace __user *_acl,
+		    size_t max_acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	struct key *key, *instkey;
+	key_ref_t key_ref;
+	long ret;
+	int i;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_VIEW);
+	if (IS_ERR(key_ref)) {
+		if (PTR_ERR(key_ref) != -EACCES)
+			return PTR_ERR(key_ref);
+
+		/* viewing a key under construction is also permitted if we
+		 * have the authorisation token handy */
+		instkey = key_get_instantiation_authkey(keyid);
+		if (IS_ERR(instkey))
+			return PTR_ERR(instkey);
+		key_put(instkey);
+
+		key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0);
+		if (IS_ERR(key_ref))
+			return PTR_ERR(key_ref);
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	down_read(&key->sem);
+	acl = key->acl;
+	refcount_inc(&acl->usage);
+	up_read(&key->sem);
+
+	if (acl->nr_ace * sizeof(struct user_key_ace) > max_acl_size)
+		goto out;
+
+	ns = current_user_ns();
+	ret = -EFAULT;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		if (put_user(ace->mask, &_acl[i].mask) < 0)
+			goto error;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (put_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto error;
+			break;
+		case KEY_ACE_UID:
+			if (put_user(from_kuid_munged(ns, ace->uid), &_acl[i].uid) < 0)
+				goto error;
+			break;
+		case KEY_ACE_GID:
+			if (put_user(from_kgid_munged(ns, ace->gid), &_acl[i].gid) < 0)
+				goto error;
+			break;
+		}
+	}
+
+out:
+	ret = acl->nr_ace * sizeof(struct user_key_ace);
+error:
+	return ret;
+}
+
+/*
+ * Get ACL from userspace.
+ */
+static struct key_acl *key_get_acl_from_user(const struct user_key_ace __user *_acl,
+					     size_t acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	long ret;
+	int nr_ace, i;
+
+	if (acl_size % sizeof(struct user_key_ace) != 0)
+		return ERR_PTR(-EINVAL);
+	nr_ace = acl_size / sizeof(struct user_key_ace);
+	if (nr_ace > 16)
+		return ERR_PTR(-EINVAL);
+
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr_ace,
+		      GFP_KERNEL);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr_ace;
+	ns = current_user_ns();
+	for (i = 0; i < nr_ace; i++) {
+		struct key_ace *ace = &acl->aces[i];
+		uid_t uid;
+		gid_t gid;
+
+		if (get_user(ace->mask, &_acl[i].mask) < 0)
+			goto fault;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (get_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto fault;
+			if (ace->special_id = 0 ||
+			    ace->special_id > KEY_ACE_NET_ADMIN)
+				goto inval;
+			break;
+		case KEY_ACE_UID:
+			if (get_user(uid, &_acl[i].uid) < 0)
+				goto fault;
+			ace->uid = make_kuid(ns, uid);
+			break;
+		case KEY_ACE_GID:
+			if (get_user(gid, &_acl[i].gid) < 0)
+				goto fault;
+			ace->gid = make_kgid(ns, gid);
+			break;
+		default:
+			goto inval;
+		}
+	}
+
+	return acl;
+
+fault:
+	ret = -EFAULT;
+	goto error;
+inval:
+	ret = -EINVAL;
+error:
+	key_put_acl(acl);
+	return ERR_PTR(ret);
+}
+
+/*
+ * Attach a new ACL to a key.
+ */
+long keyctl_set_acl(key_serial_t keyid,
+		    const struct user_key_ace __user *_acl,
+		    size_t acl_size)
+{
+	struct key_acl *acl, *discard;
+	struct key *key;
+	key_ref_t key_ref;
+	long ret;
+
+	acl = key_get_acl_from_user(_acl, acl_size);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	discard = acl;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC);
+	if (IS_ERR(key_ref)) {
+		ret = PTR_ERR(key_ref);
+		goto error_acl;
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	ret = -EACCES;
+	down_write(&key->sem);
+
+	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
+		ret = 0;
+	}
+
+	up_write(&key->sem);
+	key_put(key);
+error_acl:
+	key_put_acl(discard);
+	return ret;
+}

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-10-02 23:35     ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-10-02 23:35 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, Eric Biggers, keyrings, linux-security-module, linux-kernel

Eric Biggers <ebiggers3@gmail.com> wrote:

> This is interesting work, though it adds complexity and makes a lot of
> subtle (and potentially breaking) changes to which permissions are required
> for various things.  First I think you need to start out with a better
> statement of the problems you are trying to solve.  The patch does much more
> than simply split up the SETATTR permission --- for example, it also adds
> the ability to assign permissions to specific uids, gids, and capabilities.
> Who is planning to use those features and why?

I should split out the additional subject types (uid, gid, cap-based) into a
separate patch, but having them in one patch helped design it.

> The proposed changes to keyctl_setperm_key() actually never enable INVAL at
> all

Fixed.  Now done if KEY_xxx_SEARCH is given.

> >     will return an error if SETACL has been called on a key.
> 
> That is simplest, but it doesn't match the behavior of POSIX ACLs, for
> example.  With POSIX ACLs you can still chmod() a file that has an ACL.

Does chmod() remove a POSIX ACL from a file?

> >     The KEYCTL_DESCRIBE function then creates a permissions mask to return
> >     depending on possessor, owner, group and other ACEs, indicating
> >     SETATTR if any of INVAL, REVOKE and SET_SECURITY are set and
> >     indicating WRITE if any of WRITE, REVOKE or CLEAR are set.
> 
> Ignoring ACEs for specific users, groups, and capabilities may be problematic
> because the returned mask will under-estimate rather than over-estimate the
> permissions that have been granted.

You have a point, but KEYCTL_DESCRIBE doesn't return the set of permissions
that has been granted to the caller.  The KEYCTL_DESCRIBE interface can't be
made to accurately describe the ACL.

> With POSIX ACLs, for example, the union of all permissions that have been
> granted to any subjects other than the regular ones

Define 'regular ones'.  For the keys case, do you mean possessor, user, group
and other?

> is reflected in the group entry.  I believe that's generally considered
> better from a security perspective, because then no permissions are "hidden"
> from a listing of the regular (non-ACL) permissions only.

I'm not sure how that helps.  If there's a group ID set, then displaying extra
permissions for it because, say, there's a matching UID-specific ACE present
would be giving a false picture of the permissions set.


> >     Note that the value subsequently returned by KEYCTL_DESCRIBE may not
> >     match the value set with KEYCTL_SETATTR - but this is already true
> >     because keys that lack ->read() can't have READ set and keys that lack
> >     ->write() can't have WRITE set.
> 
> Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
> that lacks ->update().  They are only omitted from the default permissions.

You are right.  This should be fixed, assuming we don't move to some other
permissions model.

> >     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
> >     set, or if the caller has a valid instantiation auth token.
> 
> This doesn't match the code, which asks for WRITE permission only.  It's
> also a breaking change which needs to be justified on its own.  Also I'm not
> sure that WRITE permission actually makes sense, given that
> KEYCTL_SET_TIMEOUT doesn't modify the payload of the key.

I'm not sure I agree.  Whilst it doesn't modify the payload of a key, it is
*about* the lifetime of that payload IMO.

However, given that add_key() grants WRITE and SET_SECURITY/SETATTR permission
to the caller (assuming they stick the new key in a keyring they 'possess')
and request_key() grants permission directly to the upcall to set the timeout,
I wonder how much that matters.  It's just that it's not in the same class as
changing the key permissions.

> Designators into flexible arrays are a gcc extension which doesn't work with
> clang.

Fix clang?  gcc is the standard.  ;-)

> It's also difficult to read these lists of ACEs.  An ACE should read as
> "who", then "what".  But now it reads as "part of who", then "what", then
> "the rest of who".

True.  That makes it occupy less space, though.  I could also flip the order
of .mask and .special_id.

One thing I was wondering about is whether I should make the core ACLs R/O and
skip the inc/dec on the refcount if is_kernel_rodata() on the ACL is true.

> 	.aces = {
> 		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
> 		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
> 	}

Yeah, that looks nicer.

> > +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
> 
> What is a "specific subject"?

'Subject' as in the entity performing the action.  'Specific subject' as
specified by .special_id.  I'm not sure 'specific' is the right word, or
'special' for that matter, but it's how I specify 'macro' things like
'possessor' or 'this key's owner'.

Maybe KEY_ACE_FIXED_SUBJECT or KEY_ACE_MACRO_SUBJECT?  I guess KEY_ACE_UID and
KEY_ACE_GID more fit 'specific subject'.

> > +#define KEY_ACE_ROOT		5	/* The user namespace root user */
> 
> Which user namespace?
>
> > +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> > +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
> 
> In what user namespace?

cred->user_ns as per the cred passed to key_task_permission().

Having dicussed these a bit with Eric Biederman, I'm thinking I probably want
to rejig this, perhaps in favour of specifying the owner of a particular user
namespace.  However, I do think, as mentioned above, I need to split this bit
out into a subsequent patch.

> > +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> > +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
> 
> The implementations of these don't seem to be included in the patch.

As I stated.  I do have implementations of these now, which I've attached to
the bottom of this message.

> The CLEAR permission is weird because WRITE is a superset of it.  (Clearing
> a keyring is equivalent to unlinking all keys in it.)  Permissions should be
> orthogonal.

I know, but I want to be able to grant just the ability to clear a keyring to
the admin.

> Did you consider instead having one permission for adding links
> to a keyring and one for deleting links?  I'm thinking about use cases
> similar to that which the sticky bit on files is used for...

Actually, that would've been better - and possibly could've avoided the need
for invalidate - though invalidate is applied to a specific key, not a
keyring.

However, what do you do about key replacement where a link to an old key is
replaced by add_key() with a link to a new key?  Do you have to have both
permits?  Or a third REPLACE permit?

> No magic numbers please.  What are 0x3f and 0x1f?

Things I don't have symbols for.  I would like to avoid using KEY_OTH_xxx when
referring to values that weren't originally 'other'.  I didn't succeed,
though, as I'm sure you noticed, so I could just use combinations of
KEY_OTH_xxx, I guess.

> Also, this code is assuming that the subject values are numbered in a certain
> way.

The UAPI is defined so.

Further, key_task_permission() in current Linus will fail if this is not so.

> > +		if (key->type == &key_type_keyring) {
> > +			ace->mask |= KEY_ACE_JOIN;
> 
> Why is JOIN permission always given to keyrings?

It isn't, except if one calls KEYCTL_SETPERM or if a keyring is created by
join_session_keyring() or as a normal session keyrign.  add_key() creates
keyrings with default_key_acl.

> If someone has JOIN permission (or LINK permission, for that matter) on a
> keyring than they can acquire the possessor permissions.  So always giving
> JOIN defeats the point of having all the different non-possessor
> permissions...

Without this patch, join_session_keyring() doesn't impose any permissions on
joining a keyring, though find_keyring_by_name() requires SEARCH permission on
a named keyring to be able to find it (and still does - though this should
perhaps be switched to JOIN also).

Yes, LINK permission can be used to 'possess' a keyring - but only if you're
granted it.

I can alter KEYCTL_SETPERM to only give KEY_ACE_JOIN if KEY_ACE_SEARCH.

The problem is that I have to supply JOIN to be backwardly compatible, which
makes it arguable that I need to add it to default_key_acl also.

I should probably make it widely available in the main patch and add a
separate patch limiting its availability.

> Granting INVAL permission is missing.

Fixed.  Setting SEARCH sets INVAL in KEYCTL_SETPERM.

> >  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> > -				  KEY_NEED_SETATTR);
> > +				  KEY_NEED_WRITE);
> 
> As mentioned earlier, this is a breaking change which needs to be justified on
> its own.

I can undo this and defer the thought to a later patch.

> Why call groups_search() when the key gid isn't valid?

Fixed.

> This is wrong because 'desired_perm' may contain multiple permissions ---

No, it can't.  I've fixed the comment.

It was never specified what was meant by specifying multiple permissions and I
don't think I ever used that directly.  The nearest I came was to make
multiple calls to key_{,task_}permission() so that it was obvious in the
caller what was meant.

> The handling for REVOKE is weird, since it makes it possible that by *adding*
> permissions, it can appear that WRITE permission was removed.

I know.  But there's no way to render an ACL down to an old-style permissions
mask accurately.  Revocation permission was enabled by having either SETATTR
or WRITE permission, but WRITE permission was disabled under some
circumstances if ->write() didn't exist.

> Also, why isn't JOIN handled here?

What would you represent it as?

Currently, the nearest thing to a JOIN permission is SEARCH, since you need
that to find the keyring by name in the first place; beyond that, there is no
permission needed to join a keyring.

I can make JOIN enable SEARCH, but if you pass it back to KEYCTL_SETPERM,
you're going to enable SEARCH and JOIN, even if you didn't have SEARCH before.

There isn't an easy answer.

> Again, ignoring the "non-special" ACEs will cause the returned permissions
> mask to be an under-estimate rather than an over-estimate.  I'm not sure
> that's a good idea.

There isn't really a viable alternative.  perm isn't what you, the caller,
have available to you.

> > +void key_put_acl(struct key_acl *acl)
> > +{
> > +	if (refcount_dec_and_test(&acl->usage))
> > +		call_rcu(&acl->rcu, key_acl_rcu);
> > +}
> 
> Use kfree_rcu().

Good idea.  I was thinking that could only deal with objects in which the
rcu_head was first, but that doesn't appear to be the case.

> Bug: this will break from the loop if it encounters an ACE for uid or gid 4
> (the value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL
> before special_id == KEY_ACE_POSSESSOR can be considered meaningful.

Fixed.

> It's not obvious what 'tp' means.  How about:
> 
> static struct key_acl thread_keyring_acl = {
> 	...
> };
> 
> #define process_keyring_acl thread_keyring_acl

I dislike that.  I've changed it to:

static struct key_acl thread_and_process_keyring_acl = ...

> Why give JOIN permission to anonymous session keyrings? They shouldn't be
> joinable --- and they weren't prior to this patch, since they were *not* given
> KEY_USR_SEARCH permission.

Point.  Fixed.

> This is also a behavior change which needs to be explained and justified on
> its own.  A named keyring created with keyctl_join_session_keyring(name) was
> not previously joinable by default, but after this patch it is.

Yeah - this should be its own patch.  If you create a named keyring with
KEYCTL_JOIN_SESSION_KEYRING, doing this again in another process with the same
name ought to join the first one if it's owned by you and lets you find it.

> find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
> JOIN as well.  Is it intentional that two different permissions are being
> checked?

As previously mentioned, just JOIN should probably be used.  In effect, SEARCH
is also being split from JOIN.

> > +	if (perm & KEY_NEED_INVAL)
> > +		oldstyle_perm |= KEY_NEED_SEARCH;
> 
> Isn't this going to need to be SETATTR rather than SEARCH?

No.  Current code requires SEARCH (or CAP_SYS_ADMIN +
KEY_FLAG_ROOT_CAN_INVAL), not SETATTR.  It was *your* proposal to make it use
SETATTR instead.

> Even if we can't update SELinux to be aware of the new-style permissions we
> still need to fix the "search also means delete" bug.  Otherwise it will
> remain impossible to use SELinux to enforce a read-only view of a key
> hierarchy.

SELinux will need fixing if you want to be able to do that.  I need to discuss
how to do this with the SELinux people.

> > +	if (perm & KEY_NEED_REVOKE)
> > +		oldstyle_perm |= KEY_NEED_WRITE;
> 
> Another breaking change which needs to be explained and justified.  Before
> either the write or setattr SELinux key permissions was sufficient for
> revocation, but now only write is.

Fixed.

> > +	if (perm & KEY_NEED_SETSEC)
> > +		oldstyle_perm |= 0x20;
> 
> Magic number.

The symbol no longer exists.  I've #defined OLD_KEY_NEED_SETATTR to it in
hooks.c.

> > +	if (perm & KEY_NEED_JOIN)
> > +		oldstyle_perm |= KEY_NEED_LINK;
> 
> The old-style permission for joining keyrings was SEARCH, not LINK.
> Probably it *should* have been LINK, but it's still a breaking change which
> needs to be justified on its own...

Changed to SEARCH.

> Why does Smack ignore requests for SEARCH permission?

No idea.

David

---
diff --git a/security/keys/compat.c b/security/keys/compat.c
index e87c89c0177c..4a6918f68f6b 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -141,6 +141,12 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 		return keyctl_restrict_keyring(arg2, compat_ptr(arg3),
 					       compat_ptr(arg4));
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl(arg2, compat_ptr(arg3), arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl(arg2, compat_ptr(arg3), arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index a699c5937cbe..1c6efdf04445 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -302,6 +302,14 @@ static inline long compat_keyctl_dh_compute(
 #endif
 #endif
 
+extern long keyctl_get_acl(key_serial_t keyid,
+			   struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+extern long keyctl_set_acl(key_serial_t keyid,
+			   const struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+
+
 /*
  * Debugging key validation
  */
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 51fefec80cce..2773461c96ec 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -961,6 +961,8 @@ long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 			ace->mask |= KEY_ACE_REVOKE;
 		if (subset & KEY_OTH_SETATTR)
 			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (subset & KEY_OTH_SEARCH)
+			ace->mask |= KEY_ACE_INVAL;
 		if (key->type == &key_type_keyring) {
 			ace->mask |= KEY_ACE_JOIN;
 			if (subset & KEY_OTH_WRITE)
@@ -1768,6 +1770,16 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 					       (const char __user *) arg3,
 					       (const char __user *) arg4);
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl((key_serial_t)arg2,
+				      (struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl((key_serial_t)arg2,
+				      (const struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 5e186b0f802d..7056ae3e8d8d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/security.h>
 #include <linux/user_namespace.h>
+#include <linux/uaccess.h>
 #include "internal.h"
 
 struct key_acl default_key_acl = {
@@ -243,3 +244,184 @@ void key_put_acl(struct key_acl *acl)
 	if (refcount_dec_and_test(&acl->usage))
 		call_rcu(&acl->rcu, key_acl_rcu);
 }
+
+/*
+ * Get the ACL attached to key.
+ */
+long keyctl_get_acl(key_serial_t keyid,
+		    struct user_key_ace __user *_acl,
+		    size_t max_acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	struct key *key, *instkey;
+	key_ref_t key_ref;
+	long ret;
+	int i;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_VIEW);
+	if (IS_ERR(key_ref)) {
+		if (PTR_ERR(key_ref) != -EACCES)
+			return PTR_ERR(key_ref);
+
+		/* viewing a key under construction is also permitted if we
+		 * have the authorisation token handy */
+		instkey = key_get_instantiation_authkey(keyid);
+		if (IS_ERR(instkey))
+			return PTR_ERR(instkey);
+		key_put(instkey);
+
+		key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0);
+		if (IS_ERR(key_ref))
+			return PTR_ERR(key_ref);
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	down_read(&key->sem);
+	acl = key->acl;
+	refcount_inc(&acl->usage);
+	up_read(&key->sem);
+
+	if (acl->nr_ace * sizeof(struct user_key_ace) > max_acl_size)
+		goto out;
+
+	ns = current_user_ns();
+	ret = -EFAULT;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		if (put_user(ace->mask, &_acl[i].mask) < 0)
+			goto error;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (put_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto error;
+			break;
+		case KEY_ACE_UID:
+			if (put_user(from_kuid_munged(ns, ace->uid), &_acl[i].uid) < 0)
+				goto error;
+			break;
+		case KEY_ACE_GID:
+			if (put_user(from_kgid_munged(ns, ace->gid), &_acl[i].gid) < 0)
+				goto error;
+			break;
+		}
+	}
+
+out:
+	ret = acl->nr_ace * sizeof(struct user_key_ace);
+error:
+	return ret;
+}
+
+/*
+ * Get ACL from userspace.
+ */
+static struct key_acl *key_get_acl_from_user(const struct user_key_ace __user *_acl,
+					     size_t acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	long ret;
+	int nr_ace, i;
+
+	if (acl_size % sizeof(struct user_key_ace) != 0)
+		return ERR_PTR(-EINVAL);
+	nr_ace = acl_size / sizeof(struct user_key_ace);
+	if (nr_ace > 16)
+		return ERR_PTR(-EINVAL);
+
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr_ace,
+		      GFP_KERNEL);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr_ace;
+	ns = current_user_ns();
+	for (i = 0; i < nr_ace; i++) {
+		struct key_ace *ace = &acl->aces[i];
+		uid_t uid;
+		gid_t gid;
+
+		if (get_user(ace->mask, &_acl[i].mask) < 0)
+			goto fault;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (get_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto fault;
+			if (ace->special_id == 0 ||
+			    ace->special_id > KEY_ACE_NET_ADMIN)
+				goto inval;
+			break;
+		case KEY_ACE_UID:
+			if (get_user(uid, &_acl[i].uid) < 0)
+				goto fault;
+			ace->uid = make_kuid(ns, uid);
+			break;
+		case KEY_ACE_GID:
+			if (get_user(gid, &_acl[i].gid) < 0)
+				goto fault;
+			ace->gid = make_kgid(ns, gid);
+			break;
+		default:
+			goto inval;
+		}
+	}
+
+	return acl;
+
+fault:
+	ret = -EFAULT;
+	goto error;
+inval:
+	ret = -EINVAL;
+error:
+	key_put_acl(acl);
+	return ERR_PTR(ret);
+}
+
+/*
+ * Attach a new ACL to a key.
+ */
+long keyctl_set_acl(key_serial_t keyid,
+		    const struct user_key_ace __user *_acl,
+		    size_t acl_size)
+{
+	struct key_acl *acl, *discard;
+	struct key *key;
+	key_ref_t key_ref;
+	long ret;
+
+	acl = key_get_acl_from_user(_acl, acl_size);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	discard = acl;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC);
+	if (IS_ERR(key_ref)) {
+		ret = PTR_ERR(key_ref);
+		goto error_acl;
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	ret = -EACCES;
+	down_write(&key->sem);
+
+	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
+		ret = 0;
+	}
+
+	up_write(&key->sem);
+	key_put(key);
+error_acl:
+	key_put_acl(discard);
+	return ret;
+}

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL
@ 2017-10-02 23:35     ` David Howells
  0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2017-10-02 23:35 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> This is interesting work, though it adds complexity and makes a lot of
> subtle (and potentially breaking) changes to which permissions are required
> for various things.  First I think you need to start out with a better
> statement of the problems you are trying to solve.  The patch does much more
> than simply split up the SETATTR permission --- for example, it also adds
> the ability to assign permissions to specific uids, gids, and capabilities.
> Who is planning to use those features and why?

I should split out the additional subject types (uid, gid, cap-based) into a
separate patch, but having them in one patch helped design it.

> The proposed changes to keyctl_setperm_key() actually never enable INVAL at
> all

Fixed.  Now done if KEY_xxx_SEARCH is given.

> >     will return an error if SETACL has been called on a key.
> 
> That is simplest, but it doesn't match the behavior of POSIX ACLs, for
> example.  With POSIX ACLs you can still chmod() a file that has an ACL.

Does chmod() remove a POSIX ACL from a file?

> >     The KEYCTL_DESCRIBE function then creates a permissions mask to return
> >     depending on possessor, owner, group and other ACEs, indicating
> >     SETATTR if any of INVAL, REVOKE and SET_SECURITY are set and
> >     indicating WRITE if any of WRITE, REVOKE or CLEAR are set.
> 
> Ignoring ACEs for specific users, groups, and capabilities may be problematic
> because the returned mask will under-estimate rather than over-estimate the
> permissions that have been granted.

You have a point, but KEYCTL_DESCRIBE doesn't return the set of permissions
that has been granted to the caller.  The KEYCTL_DESCRIBE interface can't be
made to accurately describe the ACL.

> With POSIX ACLs, for example, the union of all permissions that have been
> granted to any subjects other than the regular ones

Define 'regular ones'.  For the keys case, do you mean possessor, user, group
and other?

> is reflected in the group entry.  I believe that's generally considered
> better from a security perspective, because then no permissions are "hidden"
> from a listing of the regular (non-ACL) permissions only.

I'm not sure how that helps.  If there's a group ID set, then displaying extra
permissions for it because, say, there's a matching UID-specific ACE present
would be giving a false picture of the permissions set.


> >     Note that the value subsequently returned by KEYCTL_DESCRIBE may not
> >     match the value set with KEYCTL_SETATTR - but this is already true
> >     because keys that lack ->read() can't have READ set and keys that lack
> >     ->write() can't have WRITE set.
> 
> Not true; you *can* set READ on a key that lacks ->read() and WRITE on a key
> that lacks ->update().  They are only omitted from the default permissions.

You are right.  This should be fixed, assuming we don't move to some other
permissions model.

> >     The KEYCTL_SET_TIMEOUT function then is permitted if WRITE or SETSEC is
> >     set, or if the caller has a valid instantiation auth token.
> 
> This doesn't match the code, which asks for WRITE permission only.  It's
> also a breaking change which needs to be justified on its own.  Also I'm not
> sure that WRITE permission actually makes sense, given that
> KEYCTL_SET_TIMEOUT doesn't modify the payload of the key.

I'm not sure I agree.  Whilst it doesn't modify the payload of a key, it is
*about* the lifetime of that payload IMO.

However, given that add_key() grants WRITE and SET_SECURITY/SETATTR permission
to the caller (assuming they stick the new key in a keyring they 'possess')
and request_key() grants permission directly to the upcall to set the timeout,
I wonder how much that matters.  It's just that it's not in the same class as
changing the key permissions.

> Designators into flexible arrays are a gcc extension which doesn't work with
> clang.

Fix clang?  gcc is the standard.  ;-)

> It's also difficult to read these lists of ACEs.  An ACE should read as
> "who", then "what".  But now it reads as "part of who", then "what", then
> "the rest of who".

True.  That makes it occupy less space, though.  I could also flip the order
of .mask and .special_id.

One thing I was wondering about is whether I should make the core ACLs R/O and
skip the inc/dec on the refcount if is_kernel_rodata() on the ACL is true.

> 	.aces = {
> 		KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
> 		KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH),
> 	}

Yeah, that looks nicer.

> > +#define KEY_ACE_SPECIAL		0x10000000 /* A specific subject */
> 
> What is a "specific subject"?

'Subject' as in the entity performing the action.  'Specific subject' as
specified by .special_id.  I'm not sure 'specific' is the right word, or
'special' for that matter, but it's how I specify 'macro' things like
'possessor' or 'this key's owner'.

Maybe KEY_ACE_FIXED_SUBJECT or KEY_ACE_MACRO_SUBJECT?  I guess KEY_ACE_UID and
KEY_ACE_GID more fit 'specific subject'.

> > +#define KEY_ACE_ROOT		5	/* The user namespace root user */
> 
> Which user namespace?
>
> > +#define KEY_ACE_SYS_ADMIN	6	/* Anyone with CAP_SYS_ADMIN */
> > +#define KEY_ACE_NET_ADMIN	7	/* Anyone with CAP_NET_ADMIN */
> 
> In what user namespace?

cred->user_ns as per the cred passed to key_task_permission().

Having dicussed these a bit with Eric Biederman, I'm thinking I probably want
to rejig this, perhaps in favour of specifying the owner of a particular user
namespace.  However, I do think, as mentioned above, I need to split this bit
out into a subsequent patch.

> > +#define KEYCTL_GET_ACL			30	/* Get a key's ACL */
> > +#define KEYCTL_SET_ACL			31	/* Set a key's ACL */
> 
> The implementations of these don't seem to be included in the patch.

As I stated.  I do have implementations of these now, which I've attached to
the bottom of this message.

> The CLEAR permission is weird because WRITE is a superset of it.  (Clearing
> a keyring is equivalent to unlinking all keys in it.)  Permissions should be
> orthogonal.

I know, but I want to be able to grant just the ability to clear a keyring to
the admin.

> Did you consider instead having one permission for adding links
> to a keyring and one for deleting links?  I'm thinking about use cases
> similar to that which the sticky bit on files is used for...

Actually, that would've been better - and possibly could've avoided the need
for invalidate - though invalidate is applied to a specific key, not a
keyring.

However, what do you do about key replacement where a link to an old key is
replaced by add_key() with a link to a new key?  Do you have to have both
permits?  Or a third REPLACE permit?

> No magic numbers please.  What are 0x3f and 0x1f?

Things I don't have symbols for.  I would like to avoid using KEY_OTH_xxx when
referring to values that weren't originally 'other'.  I didn't succeed,
though, as I'm sure you noticed, so I could just use combinations of
KEY_OTH_xxx, I guess.

> Also, this code is assuming that the subject values are numbered in a certain
> way.

The UAPI is defined so.

Further, key_task_permission() in current Linus will fail if this is not so.

> > +		if (key->type == &key_type_keyring) {
> > +			ace->mask |= KEY_ACE_JOIN;
> 
> Why is JOIN permission always given to keyrings?

It isn't, except if one calls KEYCTL_SETPERM or if a keyring is created by
join_session_keyring() or as a normal session keyrign.  add_key() creates
keyrings with default_key_acl.

> If someone has JOIN permission (or LINK permission, for that matter) on a
> keyring than they can acquire the possessor permissions.  So always giving
> JOIN defeats the point of having all the different non-possessor
> permissions...

Without this patch, join_session_keyring() doesn't impose any permissions on
joining a keyring, though find_keyring_by_name() requires SEARCH permission on
a named keyring to be able to find it (and still does - though this should
perhaps be switched to JOIN also).

Yes, LINK permission can be used to 'possess' a keyring - but only if you're
granted it.

I can alter KEYCTL_SETPERM to only give KEY_ACE_JOIN if KEY_ACE_SEARCH.

The problem is that I have to supply JOIN to be backwardly compatible, which
makes it arguable that I need to add it to default_key_acl also.

I should probably make it widely available in the main patch and add a
separate patch limiting its availability.

> Granting INVAL permission is missing.

Fixed.  Setting SEARCH sets INVAL in KEYCTL_SETPERM.

> >  	key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
> > -				  KEY_NEED_SETATTR);
> > +				  KEY_NEED_WRITE);
> 
> As mentioned earlier, this is a breaking change which needs to be justified on
> its own.

I can undo this and defer the thought to a later patch.

> Why call groups_search() when the key gid isn't valid?

Fixed.

> This is wrong because 'desired_perm' may contain multiple permissions ---

No, it can't.  I've fixed the comment.

It was never specified what was meant by specifying multiple permissions and I
don't think I ever used that directly.  The nearest I came was to make
multiple calls to key_{,task_}permission() so that it was obvious in the
caller what was meant.

> The handling for REVOKE is weird, since it makes it possible that by *adding*
> permissions, it can appear that WRITE permission was removed.

I know.  But there's no way to render an ACL down to an old-style permissions
mask accurately.  Revocation permission was enabled by having either SETATTR
or WRITE permission, but WRITE permission was disabled under some
circumstances if ->write() didn't exist.

> Also, why isn't JOIN handled here?

What would you represent it as?

Currently, the nearest thing to a JOIN permission is SEARCH, since you need
that to find the keyring by name in the first place; beyond that, there is no
permission needed to join a keyring.

I can make JOIN enable SEARCH, but if you pass it back to KEYCTL_SETPERM,
you're going to enable SEARCH and JOIN, even if you didn't have SEARCH before.

There isn't an easy answer.

> Again, ignoring the "non-special" ACEs will cause the returned permissions
> mask to be an under-estimate rather than an over-estimate.  I'm not sure
> that's a good idea.

There isn't really a viable alternative.  perm isn't what you, the caller,
have available to you.

> > +void key_put_acl(struct key_acl *acl)
> > +{
> > +	if (refcount_dec_and_test(&acl->usage))
> > +		call_rcu(&acl->rcu, key_acl_rcu);
> > +}
> 
> Use kfree_rcu().

Good idea.  I was thinking that could only deal with objects in which the
rcu_head was first, but that doesn't appear to be the case.

> Bug: this will break from the loop if it encounters an ACE for uid or gid 4
> (the value of KEY_ACE_POSSESSOR).  It needs to check for KEY_ACE_SPECIAL
> before special_id == KEY_ACE_POSSESSOR can be considered meaningful.

Fixed.

> It's not obvious what 'tp' means.  How about:
> 
> static struct key_acl thread_keyring_acl = {
> 	...
> };
> 
> #define process_keyring_acl thread_keyring_acl

I dislike that.  I've changed it to:

static struct key_acl thread_and_process_keyring_acl = ...

> Why give JOIN permission to anonymous session keyrings? They shouldn't be
> joinable --- and they weren't prior to this patch, since they were *not* given
> KEY_USR_SEARCH permission.

Point.  Fixed.

> This is also a behavior change which needs to be explained and justified on
> its own.  A named keyring created with keyctl_join_session_keyring(name) was
> not previously joinable by default, but after this patch it is.

Yeah - this should be its own patch.  If you create a named keyring with
KEYCTL_JOIN_SESSION_KEYRING, doing this again in another process with the same
name ought to join the first one if it's owned by you and lets you find it.

> find_keyring_by_name() also checks for SEARCH permission.  Now this checks for
> JOIN as well.  Is it intentional that two different permissions are being
> checked?

As previously mentioned, just JOIN should probably be used.  In effect, SEARCH
is also being split from JOIN.

> > +	if (perm & KEY_NEED_INVAL)
> > +		oldstyle_perm |= KEY_NEED_SEARCH;
> 
> Isn't this going to need to be SETATTR rather than SEARCH?

No.  Current code requires SEARCH (or CAP_SYS_ADMIN +
KEY_FLAG_ROOT_CAN_INVAL), not SETATTR.  It was *your* proposal to make it use
SETATTR instead.

> Even if we can't update SELinux to be aware of the new-style permissions we
> still need to fix the "search also means delete" bug.  Otherwise it will
> remain impossible to use SELinux to enforce a read-only view of a key
> hierarchy.

SELinux will need fixing if you want to be able to do that.  I need to discuss
how to do this with the SELinux people.

> > +	if (perm & KEY_NEED_REVOKE)
> > +		oldstyle_perm |= KEY_NEED_WRITE;
> 
> Another breaking change which needs to be explained and justified.  Before
> either the write or setattr SELinux key permissions was sufficient for
> revocation, but now only write is.

Fixed.

> > +	if (perm & KEY_NEED_SETSEC)
> > +		oldstyle_perm |= 0x20;
> 
> Magic number.

The symbol no longer exists.  I've #defined OLD_KEY_NEED_SETATTR to it in
hooks.c.

> > +	if (perm & KEY_NEED_JOIN)
> > +		oldstyle_perm |= KEY_NEED_LINK;
> 
> The old-style permission for joining keyrings was SEARCH, not LINK.
> Probably it *should* have been LINK, but it's still a breaking change which
> needs to be justified on its own...

Changed to SEARCH.

> Why does Smack ignore requests for SEARCH permission?

No idea.

David

---
diff --git a/security/keys/compat.c b/security/keys/compat.c
index e87c89c0177c..4a6918f68f6b 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -141,6 +141,12 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 		return keyctl_restrict_keyring(arg2, compat_ptr(arg3),
 					       compat_ptr(arg4));
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl(arg2, compat_ptr(arg3), arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl(arg2, compat_ptr(arg3), arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index a699c5937cbe..1c6efdf04445 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -302,6 +302,14 @@ static inline long compat_keyctl_dh_compute(
 #endif
 #endif
 
+extern long keyctl_get_acl(key_serial_t keyid,
+			   struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+extern long keyctl_set_acl(key_serial_t keyid,
+			   const struct user_key_ace __user *_acl,
+			   size_t nr_elem);
+
+
 /*
  * Debugging key validation
  */
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 51fefec80cce..2773461c96ec 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -961,6 +961,8 @@ long keyctl_setperm_key(key_serial_t id, unsigned int perm)
 			ace->mask |= KEY_ACE_REVOKE;
 		if (subset & KEY_OTH_SETATTR)
 			ace->mask |= KEY_ACE_SET_SECURITY;
+		if (subset & KEY_OTH_SEARCH)
+			ace->mask |= KEY_ACE_INVAL;
 		if (key->type == &key_type_keyring) {
 			ace->mask |= KEY_ACE_JOIN;
 			if (subset & KEY_OTH_WRITE)
@@ -1768,6 +1770,16 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 					       (const char __user *) arg3,
 					       (const char __user *) arg4);
 
+	case KEYCTL_GET_ACL:
+		return keyctl_get_acl((key_serial_t)arg2,
+				      (struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
+	case KEYCTL_SET_ACL:
+		return keyctl_set_acl((key_serial_t)arg2,
+				      (const struct user_key_ace __user *)arg3,
+				      (size_t)arg4);
+
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 5e186b0f802d..7056ae3e8d8d 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/security.h>
 #include <linux/user_namespace.h>
+#include <linux/uaccess.h>
 #include "internal.h"
 
 struct key_acl default_key_acl = {
@@ -243,3 +244,184 @@ void key_put_acl(struct key_acl *acl)
 	if (refcount_dec_and_test(&acl->usage))
 		call_rcu(&acl->rcu, key_acl_rcu);
 }
+
+/*
+ * Get the ACL attached to key.
+ */
+long keyctl_get_acl(key_serial_t keyid,
+		    struct user_key_ace __user *_acl,
+		    size_t max_acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	struct key *key, *instkey;
+	key_ref_t key_ref;
+	long ret;
+	int i;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_VIEW);
+	if (IS_ERR(key_ref)) {
+		if (PTR_ERR(key_ref) != -EACCES)
+			return PTR_ERR(key_ref);
+
+		/* viewing a key under construction is also permitted if we
+		 * have the authorisation token handy */
+		instkey = key_get_instantiation_authkey(keyid);
+		if (IS_ERR(instkey))
+			return PTR_ERR(instkey);
+		key_put(instkey);
+
+		key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0);
+		if (IS_ERR(key_ref))
+			return PTR_ERR(key_ref);
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	down_read(&key->sem);
+	acl = key->acl;
+	refcount_inc(&acl->usage);
+	up_read(&key->sem);
+
+	if (acl->nr_ace * sizeof(struct user_key_ace) > max_acl_size)
+		goto out;
+
+	ns = current_user_ns();
+	ret = -EFAULT;
+	for (i = 0; i < acl->nr_ace; i++) {
+		const struct key_ace *ace = &acl->aces[i];
+
+		if (put_user(ace->mask, &_acl[i].mask) < 0)
+			goto error;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (put_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto error;
+			break;
+		case KEY_ACE_UID:
+			if (put_user(from_kuid_munged(ns, ace->uid), &_acl[i].uid) < 0)
+				goto error;
+			break;
+		case KEY_ACE_GID:
+			if (put_user(from_kgid_munged(ns, ace->gid), &_acl[i].gid) < 0)
+				goto error;
+			break;
+		}
+	}
+
+out:
+	ret = acl->nr_ace * sizeof(struct user_key_ace);
+error:
+	return ret;
+}
+
+/*
+ * Get ACL from userspace.
+ */
+static struct key_acl *key_get_acl_from_user(const struct user_key_ace __user *_acl,
+					     size_t acl_size)
+{
+	struct user_namespace *ns;
+	struct key_acl *acl;
+	long ret;
+	int nr_ace, i;
+
+	if (acl_size % sizeof(struct user_key_ace) != 0)
+		return ERR_PTR(-EINVAL);
+	nr_ace = acl_size / sizeof(struct user_key_ace);
+	if (nr_ace > 16)
+		return ERR_PTR(-EINVAL);
+
+	acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr_ace,
+		      GFP_KERNEL);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+
+	refcount_set(&acl->usage, 1);
+	acl->nr_ace = nr_ace;
+	ns = current_user_ns();
+	for (i = 0; i < nr_ace; i++) {
+		struct key_ace *ace = &acl->aces[i];
+		uid_t uid;
+		gid_t gid;
+
+		if (get_user(ace->mask, &_acl[i].mask) < 0)
+			goto fault;
+
+		switch (ace->mask & KEY_ACE__IDENTITY) {
+		case KEY_ACE_SPECIAL:
+			if (get_user(ace->special_id, &_acl[i].special_id) < 0)
+				goto fault;
+			if (ace->special_id == 0 ||
+			    ace->special_id > KEY_ACE_NET_ADMIN)
+				goto inval;
+			break;
+		case KEY_ACE_UID:
+			if (get_user(uid, &_acl[i].uid) < 0)
+				goto fault;
+			ace->uid = make_kuid(ns, uid);
+			break;
+		case KEY_ACE_GID:
+			if (get_user(gid, &_acl[i].gid) < 0)
+				goto fault;
+			ace->gid = make_kgid(ns, gid);
+			break;
+		default:
+			goto inval;
+		}
+	}
+
+	return acl;
+
+fault:
+	ret = -EFAULT;
+	goto error;
+inval:
+	ret = -EINVAL;
+error:
+	key_put_acl(acl);
+	return ERR_PTR(ret);
+}
+
+/*
+ * Attach a new ACL to a key.
+ */
+long keyctl_set_acl(key_serial_t keyid,
+		    const struct user_key_ace __user *_acl,
+		    size_t acl_size)
+{
+	struct key_acl *acl, *discard;
+	struct key *key;
+	key_ref_t key_ref;
+	long ret;
+
+	acl = key_get_acl_from_user(_acl, acl_size);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	discard = acl;
+
+	key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC);
+	if (IS_ERR(key_ref)) {
+		ret = PTR_ERR(key_ref);
+		goto error_acl;
+	}
+
+	key = key_ref_to_ptr(key_ref);
+
+	ret = -EACCES;
+	down_write(&key->sem);
+
+	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
+		discard = rcu_dereference_protected(key->acl,
+						    lockdep_is_held(&key->sem));
+		rcu_assign_pointer(key->acl, acl);
+		ret = 0;
+	}
+
+	up_write(&key->sem);
+	key_put(key);
+error_acl:
+	key_put_acl(discard);
+	return ret;
+}
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2017-10-02 23:35 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-09-26 23:05 [RFC][PATCH] KEYS: Replace uid/gid/perm permissions checking with ACL David Howells
2017-09-26 23:05 ` David Howells
2017-09-26 23:05 ` David Howells
2017-09-27 11:41 ` David Howells
2017-09-27 11:41   ` David Howells
2017-09-27 11:41   ` David Howells
2017-10-02  2:59   ` Eric Biggers
2017-10-02  2:59     ` Eric Biggers
2017-10-02  2:59     ` Eric Biggers
2017-10-02 23:35   ` David Howells
2017-10-02 23:35     ` David Howells
2017-10-02 23:35     ` David Howells
2017-09-27 16:10 ` [RFC][PATCH] keyutils: Add key/keyring ACL support David Howells
2017-09-27 16:10   ` David Howells
2017-09-27 16:10   ` David Howells

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.