summaryrefslogtreecommitdiffstats
path: root/security
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2019-06-27 23:03:07 +0100
committerDavid Howells <dhowells@redhat.com>2019-06-27 23:03:07 +0100
commit2e12256b9a76584fa3a6da19210509d4775aee36 (patch)
treed25d8a5868dadab6086230223afeb9d26fac001b /security
parenta58946c158a040068e7c94dc1d58bbd273258068 (diff)
downloadlinux-2e12256b9a76584fa3a6da19210509d4775aee36.tar.bz2
keys: Replace uid/gid/perm permissions checking with an ACL
Replace the uid/gid/perm permissions checking on a key with an ACL to allow the SETATTR and SEARCH permissions to be split. This will also allow a greater range of subjects to represented. ============ WHY DO THIS? ============ The problem is that SETATTR and SEARCH cover a slew of actions, not all of which should be grouped together. For SETATTR, this includes actions that are about controlling access to a key: (1) Changing a key's ownership. (2) Changing a key's security information. (3) Setting a keyring's restriction. And actions that are about managing a key's lifetime: (4) Setting an expiry time. (5) Revoking a key. and (proposed) managing a key as part of a cache: (6) Invalidating a key. Managing a key's lifetime doesn't really have anything to do with controlling access to that key. Expiry time is awkward since it's more about the lifetime of the content and so, in some ways goes better with WRITE permission. It can, however, be set unconditionally by a process with an appropriate authorisation token for instantiating a key, and can also be set by the key type driver when a key is instantiated, so lumping it with the access-controlling actions is probably okay. As for SEARCH permission, that currently covers: (1) Finding keys in a keyring tree during a search. (2) Permitting keyrings to be joined. (3) Invalidation. But these don't really belong together either, since these actions really need to be controlled separately. Finally, there are number of special cases to do with granting the administrator special rights to invalidate or clear keys that I would like to handle with the ACL rather than key flags and special checks. =============== WHAT IS CHANGED =============== The SETATTR permission is split to create two new permissions: (1) SET_SECURITY - which allows the key's owner, group and ACL to be changed and a restriction to be placed on a keyring. (2) REVOKE - which allows a key to be revoked. The SEARCH permission is split to create: (1) SEARCH - which allows a keyring to be search and a key to be found. (2) JOIN - which allows a keyring to be joined as a session keyring. (3) INVAL - which allows a key to be invalidated. The WRITE permission is also split to create: (1) WRITE - which allows a key's content to be altered and links to be added, removed and replaced in a keyring. (2) CLEAR - which allows a keyring to be cleared completely. This is split out to make it possible to give just this to an administrator. (3) REVOKE - see above. Keys acquire ACLs which consist of a series of ACEs, and all that apply are unioned together. An ACE specifies a subject, such as: (*) Possessor - permitted to anyone who 'possesses' a key (*) Owner - permitted to the key owner (*) Group - permitted to the key group (*) Everyone - permitted to everyone Note that 'Other' has been replaced with 'Everyone' on the assumption that you wouldn't grant a permit to 'Other' that you wouldn't also grant to everyone else. Further subjects may be made available by later patches. The ACE also specifies a permissions mask. The set of permissions is now: VIEW Can view the key metadata READ Can read the key content WRITE Can update/modify the key content SEARCH Can find the key by searching/requesting LINK Can make a link to the key SET_SECURITY Can change owner, ACL, expiry INVAL Can invalidate REVOKE Can revoke JOIN Can join this keyring CLEAR Can clear this keyring The KEYCTL_SETPERM function is then deprecated. The KEYCTL_SET_TIMEOUT function then is permitted if SET_SECURITY 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. ====================== BACKWARD COMPATIBILITY ====================== To maintain backward compatibility, KEYCTL_SETPERM will translate the permissions mask it is given into a new ACL for a key - unless KEYCTL_SET_ACL has been called on that key, in which case an error will be returned. It will convert possessor, owner, group and other permissions into separate ACEs, if each portion of the mask is non-zero. 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 KEYCTL_DESCRIBE function translates the ACL back into a permissions mask to return depending on possessor, owner, group and everyone ACEs. It will make the following mappings: (1) INVAL, JOIN -> SEARCH (2) SET_SECURITY -> SETATTR (3) REVOKE -> WRITE if SETATTR isn't already set (4) CLEAR -> WRITE Note that the value subsequently returned by KEYCTL_DESCRIBE may not match the value set with KEYCTL_SETATTR. ======= TESTING ======= This passes the keyutils testsuite for all but a couple of tests: (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. Signed-off-by: David Howells <dhowells@redhat.com>
Diffstat (limited to 'security')
-rw-r--r--security/integrity/digsig.c31
-rw-r--r--security/integrity/digsig_asymmetric.c2
-rw-r--r--security/integrity/evm/evm_crypto.c2
-rw-r--r--security/integrity/ima/ima_mok.c13
-rw-r--r--security/integrity/integrity.h6
-rw-r--r--security/integrity/platform_certs/platform_keyring.c14
-rw-r--r--security/keys/encrypted-keys/encrypted.c2
-rw-r--r--security/keys/encrypted-keys/masterkey_trusted.c2
-rw-r--r--security/keys/gc.c2
-rw-r--r--security/keys/internal.h11
-rw-r--r--security/keys/key.c29
-rw-r--r--security/keys/keyctl.c96
-rw-r--r--security/keys/keyring.c27
-rw-r--r--security/keys/permission.c242
-rw-r--r--security/keys/persistent.c27
-rw-r--r--security/keys/proc.c22
-rw-r--r--security/keys/process_keys.c86
-rw-r--r--security/keys/request_key.c34
-rw-r--r--security/keys/request_key_auth.c15
-rw-r--r--security/selinux/hooks.c16
-rw-r--r--security/smack/smack_lsm.c3
21 files changed, 496 insertions, 186 deletions
diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c
index e19c2eb72c51..3bd2cc28f4f5 100644
--- a/security/integrity/digsig.c
+++ b/security/integrity/digsig.c
@@ -51,7 +51,8 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen,
if (!keyring[id]) {
keyring[id] =
- request_key(&key_type_keyring, keyring_name[id], NULL);
+ request_key(&key_type_keyring, keyring_name[id],
+ NULL, NULL);
if (IS_ERR(keyring[id])) {
int err = PTR_ERR(keyring[id]);
pr_err("no %s keyring: %d\n", keyring_name[id], err);
@@ -73,14 +74,14 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen,
return -EOPNOTSUPP;
}
-static int __integrity_init_keyring(const unsigned int id, key_perm_t perm,
+static int __integrity_init_keyring(const unsigned int id, struct key_acl *acl,
struct key_restriction *restriction)
{
const struct cred *cred = current_cred();
int err = 0;
keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0),
- KGIDT_INIT(0), cred, perm,
+ KGIDT_INIT(0), cred, acl,
KEY_ALLOC_NOT_IN_QUOTA, restriction, NULL);
if (IS_ERR(keyring[id])) {
err = PTR_ERR(keyring[id]);
@@ -98,10 +99,7 @@ static int __integrity_init_keyring(const unsigned int id, key_perm_t perm,
int __init integrity_init_keyring(const unsigned int id)
{
struct key_restriction *restriction;
- key_perm_t perm;
-
- perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW
- | KEY_USR_READ | KEY_USR_SEARCH;
+ struct key_acl *acl = &internal_keyring_acl;
if (id == INTEGRITY_KEYRING_PLATFORM) {
restriction = NULL;
@@ -116,14 +114,14 @@ int __init integrity_init_keyring(const unsigned int id)
return -ENOMEM;
restriction->check = restrict_link_to_ima;
- perm |= KEY_USR_WRITE;
+ acl = &internal_writable_keyring_acl;
out:
- return __integrity_init_keyring(id, perm, restriction);
+ return __integrity_init_keyring(id, acl, restriction);
}
-int __init integrity_add_key(const unsigned int id, const void *data,
- off_t size, key_perm_t perm)
+static int __init integrity_add_key(const unsigned int id, const void *data,
+ off_t size, struct key_acl *acl)
{
key_ref_t key;
int rc = 0;
@@ -132,7 +130,7 @@ int __init integrity_add_key(const unsigned int id, const void *data,
return -EINVAL;
key = key_create_or_update(make_key_ref(keyring[id], 1), "asymmetric",
- NULL, data, size, perm,
+ NULL, data, size, acl ?: &internal_key_acl,
KEY_ALLOC_NOT_IN_QUOTA);
if (IS_ERR(key)) {
rc = PTR_ERR(key);
@@ -152,7 +150,6 @@ int __init integrity_load_x509(const unsigned int id, const char *path)
void *data;
loff_t size;
int rc;
- key_perm_t perm;
rc = kernel_read_file_from_path(path, &data, &size, 0,
READING_X509_CERTIFICATE);
@@ -161,21 +158,19 @@ int __init integrity_load_x509(const unsigned int id, const char *path)
return rc;
}
- perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ;
-
pr_info("Loading X.509 certificate: %s\n", path);
- rc = integrity_add_key(id, (const void *)data, size, perm);
+ rc = integrity_add_key(id, data, size, NULL);
vfree(data);
return rc;
}
int __init integrity_load_cert(const unsigned int id, const char *source,
- const void *data, size_t len, key_perm_t perm)
+ const void *data, size_t len, struct key_acl *acl)
{
if (!data)
return -EINVAL;
pr_info("Loading X.509 certificate: %s\n", source);
- return integrity_add_key(id, data, len, perm);
+ return integrity_add_key(id, data, len, acl);
}
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 358f614811e8..a8bd8b2f4fce 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -57,7 +57,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
else
key = key_ref_to_ptr(kref);
} else {
- key = request_key(&key_type_asymmetric, name, NULL);
+ key = request_key(&key_type_asymmetric, name, NULL, NULL);
}
if (IS_ERR(key)) {
diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c
index e11564eb645b..304cb0b21f7a 100644
--- a/security/integrity/evm/evm_crypto.c
+++ b/security/integrity/evm/evm_crypto.c
@@ -356,7 +356,7 @@ int evm_init_key(void)
struct encrypted_key_payload *ekp;
int rc;
- evm_key = request_key(&key_type_encrypted, EVMKEY, NULL);
+ evm_key = request_key(&key_type_encrypted, EVMKEY, NULL, NULL);
if (IS_ERR(evm_key))
return -ENOENT;
diff --git a/security/integrity/ima/ima_mok.c b/security/integrity/ima/ima_mok.c
index 073ddc9bce5b..ce48303cfacc 100644
--- a/security/integrity/ima/ima_mok.c
+++ b/security/integrity/ima/ima_mok.c
@@ -21,6 +21,15 @@
#include <keys/system_keyring.h>
+static struct key_acl integrity_blacklist_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | KEY_ACE_SEARCH),
+ }
+};
+
struct key *ima_blacklist_keyring;
/*
@@ -40,9 +49,7 @@ __init int ima_mok_init(void)
ima_blacklist_keyring = keyring_alloc(".ima_blacklist",
KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
- (KEY_POS_ALL & ~KEY_POS_SETATTR) |
- KEY_USR_VIEW | KEY_USR_READ |
- KEY_USR_WRITE | KEY_USR_SEARCH,
+ &integrity_blacklist_keyring_acl,
KEY_ALLOC_NOT_IN_QUOTA,
restriction, NULL);
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7de59f44cba3..1c50aff6f65a 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -17,6 +17,8 @@
#include <linux/key.h>
#include <linux/audit.h>
+struct key_acl;
+
/* iint action cache flags */
#define IMA_MEASURE 0x00000001
#define IMA_MEASURED 0x00000002
@@ -154,7 +156,7 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen,
int __init integrity_init_keyring(const unsigned int id);
int __init integrity_load_x509(const unsigned int id, const char *path);
int __init integrity_load_cert(const unsigned int id, const char *source,
- const void *data, size_t len, key_perm_t perm);
+ const void *data, size_t len, struct key_acl *acl);
#else
static inline int integrity_digsig_verify(const unsigned int id,
@@ -172,7 +174,7 @@ static inline int integrity_init_keyring(const unsigned int id)
static inline int __init integrity_load_cert(const unsigned int id,
const char *source,
const void *data, size_t len,
- key_perm_t perm)
+ struct key_acl *acl)
{
return 0;
}
diff --git a/security/integrity/platform_certs/platform_keyring.c b/security/integrity/platform_certs/platform_keyring.c
index bcafd7387729..7646e35f2d91 100644
--- a/security/integrity/platform_certs/platform_keyring.c
+++ b/security/integrity/platform_certs/platform_keyring.c
@@ -14,6 +14,15 @@
#include <linux/slab.h>
#include "../integrity.h"
+static struct key_acl platform_key_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ),
+ KEY_OWNER_ACE(KEY_ACE_VIEW),
+ }
+};
+
/**
* add_to_platform_keyring - Add to platform keyring without validation.
* @source: Source of key
@@ -26,13 +35,10 @@
void __init add_to_platform_keyring(const char *source, const void *data,
size_t len)
{
- key_perm_t perm;
int rc;
- perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW;
-
rc = integrity_load_cert(INTEGRITY_KEYRING_PLATFORM, source, data, len,
- perm);
+ &platform_key_acl);
if (rc)
pr_info("Error adding keys to platform keyring %s\n", source);
}
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 1b1456b21a93..dc76c60a27a6 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -307,7 +307,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 dc3d18cae642..3322e7eeafce 100644
--- a/security/keys/encrypted-keys/masterkey_trusted.c
+++ b/security/keys/encrypted-keys/masterkey_trusted.c
@@ -33,7 +33,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 83d279fb7793..3b13fb62827f 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)
key_user_put(key->user);
key_put_tag(key->domain_tag);
+ key_put_acl(rcu_access_pointer(key->acl));
kfree(key->description);
memzero_explicit(key, sizeof(*key));
@@ -224,7 +225,6 @@ continue_scanning:
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 f1f2b076f3a1..9375d6289bb9 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -88,8 +88,11 @@ 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 void key_set_index_key(struct keyring_index_key *index_key);
+
extern struct key_type *key_type_lookup(const char *type);
extern void key_type_put(struct key_type *ktype);
@@ -160,6 +163,7 @@ 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);
@@ -183,7 +187,10 @@ 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 long key_set_acl(struct key *key, 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.
@@ -230,7 +237,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 85fdc2ea6c14..bb96d6235ea2 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -199,7 +199,7 @@ serial_exists:
* @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 @@ serial_exists:
*/
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)
@@ -296,7 +299,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;
key->last_used_at = ktime_get_real_seconds();
@@ -791,7 +795,7 @@ error:
* @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
@@ -814,7 +818,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 = {
@@ -911,22 +915,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 d2f8eabcbcf4..c8911b430e59 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -134,8 +134,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);
@@ -225,7 +224,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type,
/* do the search */
key = request_key_and_link(ktype, description, NULL, 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);
@@ -387,16 +387,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);
@@ -430,7 +424,7 @@ 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);
@@ -475,7 +469,7 @@ 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);
@@ -650,6 +644,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;
@@ -679,6 +674,10 @@ okay:
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,
@@ -686,7 +685,7 @@ okay:
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);
@@ -903,7 +902,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;
@@ -998,18 +997,25 @@ quota_overrun:
* 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;
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;
@@ -1017,17 +1023,45 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
key = key_ref_to_ptr(key_ref);
- /* make the changes with the locks held to prevent chown/chmod races */
- ret = -EACCES;
- down_write(&key->sem);
+ ret = -EOPNOTSUPP;
+ if (test_bit(KEY_FLAG_HAS_ACL, &key->flags))
+ goto error_key;
- /* 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;
- ret = 0;
+ ret = -ENOMEM;
+ acl = kzalloc(struct_size(acl, aces, 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)) & KEY_OTH_ALL;
+
+ if (!subset)
+ continue;
+ ace->type = KEY_ACE_SUBJ_STANDARD;
+ ace->subject_id = KEY_ACE_EVERYONE + i;
+ ace->perm = subset;
+ if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR))
+ ace->perm |= KEY_ACE_REVOKE;
+ if (subset & KEY_OTH_SEARCH)
+ ace->perm |= KEY_ACE_INVAL;
+ if (key->type == &key_type_keyring) {
+ if (subset & KEY_OTH_SEARCH)
+ ace->perm |= KEY_ACE_JOIN;
+ if (subset & KEY_OTH_WRITE)
+ ace->perm |= KEY_ACE_CLEAR;
+ }
+ j++;
}
+ /* make the changes with the locks held to prevent chown/chmod races */
+ down_write(&key->sem);
+ ret = key_set_acl(key, acl);
up_write(&key->sem);
+error_key:
key_put(key);
error:
return ret;
@@ -1392,7 +1426,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_SETSEC);
if (IS_ERR(key_ref)) {
/* setting the timeout on a key under construction is permitted
* if we have the authorisation token handy */
@@ -1543,7 +1577,7 @@ long keyctl_get_security(key_serial_t keyid,
* Attempt to install the calling process's session keyring on the process's
* parent process.
*
- * The keyring must exist and must grant the caller LINK permission, and the
+ * The keyring must exist and must grant the caller JOIN permission, and the
* parent process must be single-threaded and must have the same effective
* ownership as this process and mustn't be SUID/SGID.
*
@@ -1560,7 +1594,7 @@ long keyctl_session_to_parent(void)
struct cred *cred;
int ret;
- keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK);
+ keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_JOIN);
if (IS_ERR(keyring_r))
return PTR_ERR(keyring_r);
@@ -1662,7 +1696,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);
@@ -1768,7 +1802,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 29c31585ed61..62fb26c61968 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -519,11 +519,19 @@ static long keyring_read(const struct key *keyring,
return ret;
}
-/*
- * 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)
@@ -532,7 +540,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) {
@@ -1136,10 +1144,11 @@ found:
/*
* Find a keyring with the specified name.
*
- * Only keyrings that have nonzero refcount, are not revoked, and are owned by a
- * user in the current user namespace are considered. If @uid_keyring is %true,
- * the keyring additionally must have been allocated as a user or user session
- * keyring; otherwise, it must grant Search permission directly to the caller.
+ * Only keyrings that have nonzero refcount, are not revoked, and are owned by
+ * a user in the current user namespace are considered. If @uid_keyring is
+ * %true, the keyring additionally must have been allocated as a user or user
+ * session keyring; otherwise, it must grant JOIN permission directly to the
+ * caller (ie. not through possession).
*
* Returns a pointer to the keyring with the keyring's refcount having being
* incremented on success. -ENOKEY is returned if a key could not be found.
@@ -1173,7 +1182,7 @@ struct key *find_keyring_by_name(const char *name, bool uid_keyring)
continue;
} else {
if (key_permission(make_key_ref(keyring, 0),
- KEY_NEED_SEARCH) < 0)
+ KEY_NEED_JOIN) < 0)
continue;
}
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 06df9d5e7572..e3237bb2e970 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -11,13 +11,67 @@
#include <linux/export.h>
#include <linux/security.h>
+#include <linux/user_namespace.h>
+#include <linux/uaccess.h>
#include "internal.h"
+struct key_acl default_key_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .possessor_viewable = true,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN),
+ KEY_OWNER_ACE(KEY_ACE_VIEW),
+ }
+};
+EXPORT_SYMBOL(default_key_acl);
+
+struct key_acl joinable_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .possessor_viewable = true,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_LINK | KEY_ACE_JOIN),
+ }
+};
+EXPORT_SYMBOL(joinable_keyring_acl);
+
+struct key_acl internal_key_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+ }
+};
+EXPORT_SYMBOL(internal_key_acl);
+
+struct key_acl internal_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH),
+ }
+};
+EXPORT_SYMBOL(internal_keyring_acl);
+
+struct key_acl internal_writable_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | KEY_ACE_SEARCH),
+ }
+};
+EXPORT_SYMBOL(internal_writable_keyring_acl);
+
/**
* 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 permission 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 +82,73 @@
* 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;
+
+ BUILD_BUG_ON(KEY_NEED_VIEW != KEY_ACE_VIEW ||
+ KEY_NEED_READ != KEY_ACE_READ ||
+ KEY_NEED_WRITE != KEY_ACE_WRITE ||
+ KEY_NEED_SEARCH != KEY_ACE_SEARCH ||
+ KEY_NEED_LINK != KEY_ACE_LINK ||
+ KEY_NEED_SETSEC != KEY_ACE_SET_SECURITY ||
+ KEY_NEED_INVAL != KEY_ACE_INVAL ||
+ KEY_NEED_REVOKE != KEY_ACE_REVOKE ||
+ KEY_NEED_JOIN != KEY_ACE_JOIN ||
+ KEY_NEED_CLEAR != KEY_ACE_CLEAR);
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;
- }
+ rcu_read_lock();
- /* 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;
- }
+ 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];
- ret = groups_search(cred->group_info, key->gid);
- if (ret) {
- kperm = key->perm >> 8;
- goto use_these_perms;
+ switch (ace->type) {
+ case KEY_ACE_SUBJ_STANDARD:
+ switch (ace->subject_id) {
+ case KEY_ACE_POSSESSOR:
+ if (is_key_possessed(key_ref))
+ allow |= ace->perm;
+ break;
+ case KEY_ACE_OWNER:
+ if (uid_eq(key->uid, cred->fsuid))
+ allow |= ace->perm;
+ break;
+ case KEY_ACE_GROUP:
+ if (gid_valid(key->gid)) {
+ if (gid_eq(key->gid, cred->fsgid))
+ allow |= ace->perm;
+ else if (groups_search(cred->group_info, key->gid))
+ allow |= ace->perm;
+ }
+ break;
+ case KEY_ACE_EVERYONE:
+ allow |= ace->perm;
+ break;
+ }
+ 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 +182,99 @@ int key_validate(const struct key *key)
return 0;
}
EXPORT_SYMBOL(key_validate);
+
+/*
+ * Roughly render an ACL to an old-style permissions mask. We cannot
+ * accurately render what the ACL, particularly if it has ACEs that represent
+ * subjects outside of { poss, user, group, other }.
+ */
+unsigned int key_acl_to_perm(const struct key_acl *acl)
+{
+ unsigned int perm = 0, tperm;
+ int i;
+
+ BUILD_BUG_ON(KEY_OTH_VIEW != KEY_ACE_VIEW ||
+ KEY_OTH_READ != KEY_ACE_READ ||
+ KEY_OTH_WRITE != KEY_ACE_WRITE ||
+ KEY_OTH_SEARCH != KEY_ACE_SEARCH ||
+ KEY_OTH_LINK != KEY_ACE_LINK ||
+ KEY_OTH_SETATTR != KEY_ACE_SET_SECURITY);
+
+ if (!acl || acl->nr_ace == 0)
+ return 0;
+
+ for (i = 0; i < acl->nr_ace; i++) {
+ const struct key_ace *ace = &acl->aces[i];
+
+ switch (ace->type) {
+ case KEY_ACE_SUBJ_STANDARD:
+ tperm = ace->perm & KEY_OTH_ALL;
+
+ /* Invalidation and joining were allowed by SEARCH */
+ if (ace->perm & (KEY_ACE_INVAL | KEY_ACE_JOIN))
+ tperm |= KEY_OTH_SEARCH;
+
+ /* Revocation was allowed by either SETATTR or WRITE */
+ if ((ace->perm & KEY_ACE_REVOKE) && !(tperm & KEY_OTH_SETATTR))
+ tperm |= KEY_OTH_WRITE;
+
+ /* Clearing was allowed by WRITE */
+ if (ace->perm & KEY_ACE_CLEAR)
+ tperm |= KEY_OTH_WRITE;
+
+ switch (ace->subject_id) {
+ case KEY_ACE_POSSESSOR:
+ perm |= tperm << 24;
+ break;
+ case KEY_ACE_OWNER:
+ perm |= tperm << 16;
+ break;
+ case KEY_ACE_GROUP:
+ perm |= tperm << 8;
+ break;
+ case KEY_ACE_EVERYONE:
+ perm |= tperm << 0;
+ break;
+ }
+ }
+ }
+
+ return perm;
+}
+
+/*
+ * Destroy a key's ACL.
+ */
+void key_put_acl(struct key_acl *acl)
+{
+ if (acl && refcount_dec_and_test(&acl->usage))
+ kfree_rcu(acl, rcu);
+}
+
+/*
+ * Try to set the ACL. This either attaches or discards the proposed ACL.
+ */
+long key_set_acl(struct key *key, struct key_acl *acl)
+{
+ int i;
+
+ /* 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_put_acl(acl);
+ return -EACCES;
+ }
+
+ for (i = 0; i < acl->nr_ace; i++) {
+ const struct key_ace *ace = &acl->aces[i];
+ if (ace->type == KEY_ACE_SUBJ_STANDARD &&
+ ace->subject_id == KEY_ACE_POSSESSOR) {
+ if (ace->perm & KEY_ACE_VIEW)
+ acl->possessor_viewable = true;
+ break;
+ }
+ }
+
+ rcu_swap_protected(key->acl, acl, lockdep_is_held(&key->sem));
+ key_put_acl(acl);
+ return 0;
+}
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index 9944d855a28d..c4c480f630ea 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -16,6 +16,27 @@
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 = {
+ KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ),
+ }
+};
+
+static struct key_acl persistent_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .nr_ace = 2,
+ .possessor_viewable = true,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE |
+ KEY_ACE_SEARCH | KEY_ACE_LINK |
+ KEY_ACE_CLEAR | KEY_ACE_INVAL),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ),
+ }
+};
+
/*
* Create the persistent keyring register for the current user namespace.
*
@@ -26,8 +47,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 +80,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 b4f5ba56b9cb..0056fe2dc39b 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -114,11 +114,13 @@ static struct key *find_ge_key(struct seq_file *p, key_serial_t id)
}
static void *proc_keys_start(struct seq_file *p, loff_t *_pos)
+ __acquires(rcu)
__acquires(key_serial_lock)
{
key_serial_t pos = *_pos;
struct key *key;
+ rcu_read_lock();
spin_lock(&key_serial_lock);
if (*_pos > INT_MAX)
@@ -148,12 +150,15 @@ static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos)
static void proc_keys_stop(struct seq_file *p, void *v)
__releases(key_serial_lock)
+ __releases(rcu)
{
spin_unlock(&key_serial_lock);
+ rcu_read_unlock();
}
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);
unsigned long flags;
@@ -161,6 +166,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
time64_t now, expiry;
char xbuf[16];
short state;
+ bool check_pos;
u64 timo;
int rc;
@@ -174,15 +180,15 @@ static int proc_keys_show(struct seq_file *m, void *v)
KEYRING_SEARCH_RECURSE),
};
- key_ref = make_key_ref(key, 0);
+ acl = rcu_dereference(key->acl);
+ check_pos = acl->possessor_viewable;
/* 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) {
- rcu_read_lock();
+ key_ref = make_key_ref(key, 0);
+ if (check_pos) {
skey_ref = search_cred_keyrings_rcu(&ctx);
- rcu_read_unlock();
if (!IS_ERR(skey_ref)) {
key_ref_put(skey_ref);
key_ref = make_key_ref(key, 1);
@@ -192,12 +198,10 @@ static int proc_keys_show(struct seq_file *m, void *v)
/* check whether the current task is allowed to view the key */
rc = key_task_permission(key_ref, ctx.cred, KEY_NEED_VIEW);
if (rc < 0)
- return 0;
+ goto out;
now = ktime_get_real_seconds();
- rcu_read_lock();
-
/* come up with a suitable timeout value */
expiry = READ_ONCE(key->expiry);
if (expiry == 0) {
@@ -236,7 +240,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
showflag(flags, '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);
@@ -247,7 +251,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
key->type->describe(key, m);
seq_putc(m, '\n');
- rcu_read_unlock();
+out:
return 0;
}
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index f74d64215942..ddda8544630d 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -36,6 +36,47 @@ struct key_user root_key_user = {
.uid = GLOBAL_ROOT_UID,
};
+static struct key_acl user_reg_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .possessor_viewable = true,
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_WRITE | KEY_ACE_SEARCH),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ),
+ }
+};
+
+static struct key_acl user_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .possessor_viewable = true,
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE |
+ KEY_ACE_SEARCH | KEY_ACE_LINK),
+ KEY_OWNER_ACE(KEY_ACE__PERMS & ~(KEY_ACE_JOIN | KEY_ACE_SET_SECURITY)),
+ }
+};
+
+static struct key_acl session_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .possessor_viewable = true,
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN),
+ KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ),
+ }
+};
+
+static struct key_acl thread_and_process_keyring_acl = {
+ .usage = REFCOUNT_INIT(1),
+ .possessor_viewable = true,
+ .nr_ace = 2,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~(KEY_ACE_JOIN | KEY_ACE_SET_SECURITY)),
+ KEY_OWNER_ACE(KEY_ACE_VIEW),
+ }
+};
+
/*
* Get or create a user register keyring.
*/
@@ -55,11 +96,8 @@ static struct key *get_user_register(struct user_namespace *user_ns)
if (!reg_keyring) {
reg_keyring = keyring_alloc(".user_reg",
user_ns->owner, INVALID_GID,
- &init_cred,
- KEY_POS_WRITE | KEY_POS_SEARCH |
- KEY_USR_VIEW | KEY_USR_READ,
- 0,
- NULL, NULL);
+ &init_cred, &user_reg_keyring_acl,
+ 0, NULL, NULL);
if (!IS_ERR(reg_keyring))
smp_store_release(&user_ns->user_keyring_register,
reg_keyring);
@@ -81,14 +119,11 @@ int look_up_user_keyrings(struct key **_user_keyring,
const struct cred *cred = current_cred();
struct user_namespace *user_ns = current_user_ns();
struct key *reg_keyring, *uid_keyring, *session_keyring;
- key_perm_t user_keyring_perm;
key_ref_t uid_keyring_r, session_keyring_r;
uid_t uid = from_kuid(user_ns, cred->user->uid);
char buf[20];
int ret;
- user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
-
kenter("%u", uid);
reg_keyring = get_user_register(user_ns);
@@ -108,7 +143,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
kdebug("_uid %p", uid_keyring_r);
if (uid_keyring_r == ERR_PTR(-EAGAIN)) {
uid_keyring = keyring_alloc(buf, cred->user->uid, INVALID_GID,
- cred, user_keyring_perm,
+ cred, &user_keyring_acl,
KEY_ALLOC_UID_KEYRING |
KEY_ALLOC_IN_QUOTA,
NULL, reg_keyring);
@@ -130,7 +165,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
kdebug("_uid_ses %p", session_keyring_r);
if (session_keyring_r == ERR_PTR(-EAGAIN)) {
session_keyring = keyring_alloc(buf, cred->user->uid, INVALID_GID,
- cred, user_keyring_perm,
+ cred, &user_keyring_acl,
KEY_ALLOC_UID_KEYRING |
KEY_ALLOC_IN_QUOTA,
NULL, NULL);
@@ -230,7 +265,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,
+ &thread_and_process_keyring_acl,
KEY_ALLOC_QUOTA_OVERRUN,
NULL, NULL);
if (IS_ERR(keyring))
@@ -277,7 +312,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,
+ &thread_and_process_keyring_acl,
KEY_ALLOC_QUOTA_OVERRUN,
NULL, NULL);
if (IS_ERR(keyring))
@@ -332,8 +367,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 {
@@ -613,7 +647,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,
@@ -788,12 +822,12 @@ try_again:
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;
@@ -805,9 +839,11 @@ try_again:
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 = ktime_get_real_seconds();
@@ -872,13 +908,13 @@ 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);
goto error2;
}
+ goto no_perm_test;
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
@@ -887,6 +923,12 @@ 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;
+
+no_perm_test:
/* 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 aa589d3c90e2..64af697a9126 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -139,8 +139,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
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);
@@ -371,11 +370,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 = NULL;
struct key *key;
- key_perm_t perm;
key_ref_t key_ref;
int ret;
@@ -385,17 +384,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);
+ acl, flags, NULL);
if (IS_ERR(key))
goto alloc_failed;
@@ -478,6 +469,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)
{
@@ -500,7 +492,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx,
goto error_put_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) {
@@ -538,6 +530,7 @@ error:
* @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().
*
@@ -565,6 +558,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)
{
@@ -639,7 +633,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:
@@ -682,6 +676,7 @@ EXPORT_SYMBOL(wait_for_key_construction);
* @description: The searchable description of the key.
* @domain_tag: The domain in which the key operates.
* @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,
@@ -694,7 +689,8 @@ EXPORT_SYMBOL(wait_for_key_construction);
struct key *request_key_tag(struct key_type *type,
const char *description,
struct key_tag *domain_tag,
- const char *callout_info)
+ const char *callout_info,
+ struct key_acl *acl)
{
struct key *key;
size_t callout_len = 0;
@@ -704,7 +700,7 @@ struct key *request_key_tag(struct key_type *type,
callout_len = strlen(callout_info);
key = request_key_and_link(type, description, domain_tag,
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) {
@@ -724,6 +720,7 @@ EXPORT_SYMBOL(request_key_tag);
* @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.
@@ -736,14 +733,15 @@ struct key *request_key_with_auxdata(struct key_type *type,
struct key_tag *domain_tag,
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, domain_tag,
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) {
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index f613987e8a63..d9146606f54e 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -28,6 +28,17 @@ 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,
+ .possessor_viewable = true,
+ .aces = {
+ KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH |
+ KEY_ACE_LINK),
+ KEY_OWNER_ACE(KEY_ACE_VIEW),
+ }
+};
+
/*
* The request-key authorisation key type definition.
*/
@@ -214,8 +225,8 @@ struct key *request_key_auth_new(struct key *target, const char *op,
authkey = key_alloc(&key_type_request_key_auth, desc,
cred->fsuid, cred->fsgid, cred,
- KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | KEY_POS_LINK |
- 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 c61787b15f27..b828401dcb70 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6481,6 +6481,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
@@ -6489,13 +6490,26 @@ 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_SETSEC)
+ oldstyle_perm |= OLD_KEY_NEED_SETATTR;
+ if (perm & KEY_NEED_INVAL)
+ oldstyle_perm |= KEY_NEED_SEARCH;
+ if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
+ oldstyle_perm |= KEY_NEED_WRITE;
+ if (perm & KEY_NEED_JOIN)
+ oldstyle_perm |= KEY_NEED_SEARCH;
+ 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(&selinux_state,
- sid, ksec->sid, SECCLASS_KEY, perm, NULL);
+ 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 0de725f88bed..6095dc3565a5 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4285,7 +4285,8 @@ static int smack_key_permission(key_ref_t key_ref,
#endif
if (perm & (KEY_NEED_READ | KEY_NEED_SEARCH | KEY_NEED_VIEW))
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);