* [PATCH 6/9] keys: Include target namespace in match criteria [ver #4]
From: David Howells @ 2019-06-19 16:47 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Currently a key has a standard matching criteria of { type, description }
and this is used to only allow keys with unique criteria in a keyring.
This means, however, that you cannot have keys with the same type and
description but a different target namespace in the same keyring.
This is a potential problem for a containerised environment where, say, a
container is made up of some parts of its mount space involving netfs
superblocks from two different network namespaces.
This is also a problem for shared system management keyrings such as the
DNS records keyring or the NFS idmapper keyring that might contain keys
from different network namespaces.
Fix this by including a namespace component in a key's matching criteria.
Keyring types are marked to indicate which, if any, namespace is relevant
to keys of that type, and that namespace is set when the key is created
from the current task's namespace set.
The capability bit KEYCTL_CAPS1_NS_KEY_TAG is set if the kernel is
employing this feature.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/linux/key.h | 10 ++++++++++
include/uapi/linux/keyctl.h | 1 +
security/keys/gc.c | 2 +-
security/keys/key.c | 1 +
security/keys/keyctl.c | 3 ++-
security/keys/keyring.c | 36 ++++++++++++++++++++++++++++++++++--
security/keys/persistent.c | 1 +
7 files changed, 50 insertions(+), 4 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index ae1177302d70..abc68555bac3 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -82,9 +82,16 @@ struct cred;
struct key_type;
struct key_owner;
+struct key_tag;
struct keyring_list;
struct keyring_name;
+struct key_tag {
+ struct rcu_head rcu;
+ refcount_t usage;
+ bool removed; /* T when subject removed */
+};
+
struct keyring_index_key {
/* [!] If this structure is altered, the union in struct key must change too! */
unsigned long hash; /* Hash value */
@@ -101,6 +108,7 @@ struct keyring_index_key {
unsigned long x;
};
struct key_type *type;
+ struct key_tag *domain_tag; /* Domain of operation */
const char *description;
};
@@ -218,6 +226,7 @@ struct key {
unsigned long hash;
unsigned long len_desc;
struct key_type *type; /* type of key */
+ struct key_tag *domain_tag; /* Domain of operation */
char *description;
};
};
@@ -268,6 +277,7 @@ extern struct key *key_alloc(struct key_type *type,
extern void key_revoke(struct key *key);
extern void key_invalidate(struct key *key);
extern void key_put(struct key *key);
+extern bool key_put_tag(struct key_tag *tag);
static inline struct key *__key_get(struct key *key)
{
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 35b405034674..3bb5324d514f 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -129,5 +129,6 @@ struct keyctl_pkey_params {
#define KEYCTL_CAPS0_RESTRICT_KEYRING 0x40 /* KEYCTL_RESTRICT_KEYRING supported */
#define KEYCTL_CAPS0_MOVE 0x80 /* KEYCTL_MOVE supported */
#define KEYCTL_CAPS1_NS_KEYRING_NAME 0x01 /* Keyring names are per-user_namespace */
+#define KEYCTL_CAPS2_NS_KEY_TAG 0x02 /* Key indexing can include a namespace tag */
#endif /* _LINUX_KEYCTL_H */
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 634e96b380e8..83d279fb7793 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -154,7 +154,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
atomic_dec(&key->user->nikeys);
key_user_put(key->user);
-
+ key_put_tag(key->domain_tag);
kfree(key->description);
memzero_explicit(key, sizeof(*key));
diff --git a/security/keys/key.c b/security/keys/key.c
index 9d52f2472a09..85fdc2ea6c14 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -317,6 +317,7 @@ struct key *key_alloc(struct key_type *type, const char *desc,
goto security_error;
/* publish the key by giving it a serial number */
+ refcount_inc(&key->domain_tag->usage);
atomic_inc(&user->nkeys);
key_alloc_serial(key);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 8a813220f269..aa9be531e5f5 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -40,7 +40,8 @@ static const unsigned char keyrings_capabilities[2] = {
KEYCTL_CAPS0_RESTRICT_KEYRING |
KEYCTL_CAPS0_MOVE
),
- [1] = (KEYCTL_CAPS1_NS_KEYRING_NAME),
+ [1] = (KEYCTL_CAPS1_NS_KEYRING_NAME |
+ KEYCTL_CAPS2_NS_KEY_TAG),
};
static int key_get_type_from_user(char *type,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 3663e5168583..0da8fa282d56 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -175,6 +175,9 @@ static void hash_key_type_and_desc(struct keyring_index_key *index_key)
type = (unsigned long)index_key->type;
acc = mult_64x32_and_fold(type, desc_len + 13);
acc = mult_64x32_and_fold(acc, 9207);
+ piece = (unsigned long)index_key->domain_tag;
+ acc = mult_64x32_and_fold(acc, piece);
+ acc = mult_64x32_and_fold(acc, 9207);
for (;;) {
n = desc_len;
@@ -208,16 +211,36 @@ static void hash_key_type_and_desc(struct keyring_index_key *index_key)
/*
* Finalise an index key to include a part of the description actually in the
- * index key and to add in the hash too.
+ * index key, to set the domain tag and to calculate the hash.
*/
void key_set_index_key(struct keyring_index_key *index_key)
{
+ static struct key_tag default_domain_tag = { .usage = REFCOUNT_INIT(1), };
size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
+
memcpy(index_key->desc, index_key->description, n);
+ index_key->domain_tag = &default_domain_tag;
hash_key_type_and_desc(index_key);
}
+/**
+ * key_put_tag - Release a ref on a tag.
+ * @tag: The tag to release.
+ *
+ * This releases a reference the given tag and returns true if that ref was the
+ * last one.
+ */
+bool key_put_tag(struct key_tag *tag)
+{
+ if (refcount_dec_and_test(&tag->usage)) {
+ kfree_rcu(tag, rcu);
+ return true;
+ }
+
+ return false;
+}
+
/*
* Build the next index key chunk.
*
@@ -238,8 +261,10 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
return index_key->x;
case 2:
return (unsigned long)index_key->type;
+ case 3:
+ return (unsigned long)index_key->domain_tag;
default:
- level -= 3;
+ level -= 4;
if (desc_len <= sizeof(index_key->desc))
return 0;
@@ -268,6 +293,7 @@ static bool keyring_compare_object(const void *object, const void *data)
const struct key *key = keyring_ptr_to_key(object);
return key->index_key.type == index_key->type &&
+ key->index_key.domain_tag == index_key->domain_tag &&
key->index_key.desc_len == index_key->desc_len &&
memcmp(key->index_key.description, index_key->description,
index_key->desc_len) == 0;
@@ -309,6 +335,12 @@ static int keyring_diff_objects(const void *object, const void *data)
goto differ;
level += sizeof(unsigned long);
+ seg_a = (unsigned long)a->domain_tag;
+ seg_b = (unsigned long)b->domain_tag;
+ if ((seg_a ^ seg_b) != 0)
+ goto differ;
+ level += sizeof(unsigned long);
+
i = sizeof(a->desc);
if (a->desc_len <= i)
goto same;
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index 90303fe4a394..9944d855a28d 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -84,6 +84,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
long ret;
/* Look in the register if it exists */
+ memset(&index_key, 0, sizeof(index_key));
index_key.type = &key_type_keyring;
index_key.description = buf;
index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
^ permalink raw reply related
* [PATCH 5/9] keys: Move the user and user-session keyrings to the user_namespace [ver #4]
From: David Howells @ 2019-06-19 16:47 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Move the user and user-session keyrings to the user_namespace struct rather
than pinning them from the user_struct struct. This prevents these
keyrings from propagating across user-namespaces boundaries with regard to
the KEY_SPEC_* flags, thereby making them more useful in a containerised
environment.
The issue is that a single user_struct may be represent UIDs in several
different namespaces.
The way the patch does this is by attaching a 'register keyring' in each
user_namespace and then sticking the user and user-session keyrings into
that. It can then be searched to retrieve them.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Jann Horn <jannh@google.com>
---
include/linux/sched/user.h | 14 --
include/linux/user_namespace.h | 9 +
kernel/user.c | 7 -
kernel/user_namespace.c | 4 -
security/keys/internal.h | 3
security/keys/keyring.c | 1
security/keys/persistent.c | 8 +
security/keys/process_keys.c | 259 ++++++++++++++++++++++++++--------------
security/keys/request_key.c | 20 ++-
9 files changed, 196 insertions(+), 129 deletions(-)
diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h
index 468d2565a9fe..917d88edb7b9 100644
--- a/include/linux/sched/user.h
+++ b/include/linux/sched/user.h
@@ -7,8 +7,6 @@
#include <linux/refcount.h>
#include <linux/ratelimit.h>
-struct key;
-
/*
* Some day this will be a full-fledged user tracking system..
*/
@@ -30,18 +28,6 @@ struct user_struct {
unsigned long unix_inflight; /* How many files in flight in unix sockets */
atomic_long_t pipe_bufs; /* how many pages are allocated in pipe buffers */
-#ifdef CONFIG_KEYS
- /*
- * These pointers can only change from NULL to a non-NULL value once.
- * Writes are protected by key_user_keyring_mutex.
- * Unlocked readers should use READ_ONCE() unless they know that
- * install_user_keyrings() has been called successfully (which sets
- * these members to non-NULL values, preventing further modifications).
- */
- struct key *uid_keyring; /* UID specific keyring */
- struct key *session_keyring; /* UID's default session keyring */
-#endif
-
/* Hash table maintenance information */
struct hlist_node uidhash_node;
kuid_t uid;
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 90457015fa3f..fb9f4f799554 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -65,14 +65,19 @@ struct user_namespace {
unsigned long flags;
#ifdef CONFIG_KEYS
- /* List of joinable keyrings in this namespace */
+ /* List of joinable keyrings in this namespace. Modification access of
+ * these pointers is controlled by keyring_sem. Once
+ * user_keyring_register is set, it won't be changed, so it can be
+ * accessed directly with READ_ONCE().
+ */
struct list_head keyring_name_list;
+ struct key *user_keyring_register;
+ struct rw_semaphore keyring_sem;
#endif
/* Register of per-UID persistent keyrings for this namespace */
#ifdef CONFIG_PERSISTENT_KEYRINGS
struct key *persistent_keyring_register;
- struct rw_semaphore persistent_keyring_register_sem;
#endif
struct work_struct work;
#ifdef CONFIG_SYSCTL
diff --git a/kernel/user.c b/kernel/user.c
index 50979fd1b7aa..f8519b62cf9a 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -64,10 +64,7 @@ struct user_namespace init_user_ns = {
.flags = USERNS_INIT_FLAGS,
#ifdef CONFIG_KEYS
.keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
-#endif
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- .persistent_keyring_register_sem =
- __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
+ .keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem),
#endif
};
EXPORT_SYMBOL_GPL(init_user_ns);
@@ -143,8 +140,6 @@ static void free_user(struct user_struct *up, unsigned long flags)
{
uid_hash_remove(up);
spin_unlock_irqrestore(&uidhash_lock, flags);
- key_put(up->uid_keyring);
- key_put(up->session_keyring);
kmem_cache_free(uid_cachep, up);
}
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index bda6e890ad88..c87c2ecc7085 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -135,9 +135,7 @@ int create_user_ns(struct cred *new)
#ifdef CONFIG_KEYS
INIT_LIST_HEAD(&ns->keyring_name_list);
-#endif
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- init_rwsem(&ns->persistent_keyring_register_sem);
+ init_rwsem(&ns->keyring_sem);
#endif
ret = -ENOMEM;
if (!setup_userns_sysctls(ns))
diff --git a/security/keys/internal.h b/security/keys/internal.h
index aa361299a3ec..d3a9439e2386 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -148,7 +148,8 @@ extern key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx)
extern struct key *find_keyring_by_name(const char *name, bool uid_keyring);
-extern int install_user_keyrings(void);
+extern int look_up_user_keyrings(struct key **, struct key **);
+extern struct key *get_user_session_keyring_rcu(const struct cred *);
extern int install_thread_keyring_to_cred(struct cred *);
extern int install_process_keyring_to_cred(struct cred *);
extern int install_session_keyring_to_cred(struct cred *, struct key *);
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index fe851292509e..3663e5168583 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -62,6 +62,7 @@ void key_free_user_ns(struct user_namespace *ns)
list_del_init(&ns->keyring_name_list);
write_unlock(&keyring_name_lock);
+ key_put(ns->user_keyring_register);
#ifdef CONFIG_PERSISTENT_KEYRINGS
key_put(ns->persistent_keyring_register);
#endif
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index fc29ec59efa7..90303fe4a394 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -91,9 +91,9 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
if (ns->persistent_keyring_register) {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
- down_read(&ns->persistent_keyring_register_sem);
+ down_read(&ns->keyring_sem);
persistent_ref = find_key_to_update(reg_ref, &index_key);
- up_read(&ns->persistent_keyring_register_sem);
+ up_read(&ns->keyring_sem);
if (persistent_ref)
goto found;
@@ -102,9 +102,9 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
/* It wasn't in the register, so we'll need to create it. We might
* also need to create the register.
*/
- down_write(&ns->persistent_keyring_register_sem);
+ down_write(&ns->keyring_sem);
persistent_ref = key_create_persistent(ns, uid, &index_key);
- up_write(&ns->persistent_keyring_register_sem);
+ up_write(&ns->keyring_sem);
if (!IS_ERR(persistent_ref))
goto found;
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index b07f768d23dc..f74d64215942 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -19,15 +19,13 @@
#include <linux/security.h>
#include <linux/user_namespace.h>
#include <linux/uaccess.h>
+#include <linux/init_task.h>
#include <keys/request_key_auth-type.h>
#include "internal.h"
/* Session keyring create vs join semaphore */
static DEFINE_MUTEX(key_session_mutex);
-/* User keyring creation semaphore */
-static DEFINE_MUTEX(key_user_keyring_mutex);
-
/* The root user's tracking struct */
struct key_user root_key_user = {
.usage = REFCOUNT_INIT(3),
@@ -39,98 +37,185 @@ struct key_user root_key_user = {
};
/*
- * Install the user and user session keyrings for the current process's UID.
+ * Get or create a user register keyring.
+ */
+static struct key *get_user_register(struct user_namespace *user_ns)
+{
+ struct key *reg_keyring = READ_ONCE(user_ns->user_keyring_register);
+
+ if (reg_keyring)
+ return reg_keyring;
+
+ down_write(&user_ns->keyring_sem);
+
+ /* Make sure there's a register keyring. It gets owned by the
+ * user_namespace's owner.
+ */
+ reg_keyring = user_ns->user_keyring_register;
+ 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);
+ if (!IS_ERR(reg_keyring))
+ smp_store_release(&user_ns->user_keyring_register,
+ reg_keyring);
+ }
+
+ up_write(&user_ns->keyring_sem);
+
+ /* We don't return a ref since the keyring is pinned by the user_ns */
+ return reg_keyring;
+}
+
+/*
+ * Look up the user and user session keyrings for the current process's UID,
+ * creating them if they don't exist.
*/
-int install_user_keyrings(void)
+int look_up_user_keyrings(struct key **_user_keyring,
+ struct key **_user_session_keyring)
{
- struct user_struct *user;
- const struct cred *cred;
- struct key *uid_keyring, *session_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;
- 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);
- kenter("%p{%u}", user, uid);
+ kenter("%u", uid);
- if (READ_ONCE(user->uid_keyring) && READ_ONCE(user->session_keyring)) {
- kleave(" = 0 [exist]");
- return 0;
- }
+ reg_keyring = get_user_register(user_ns);
+ if (IS_ERR(reg_keyring))
+ return PTR_ERR(reg_keyring);
- mutex_lock(&key_user_keyring_mutex);
+ down_write(&user_ns->keyring_sem);
ret = 0;
- if (!user->uid_keyring) {
- /* get the UID-specific keyring
- * - there may be one in existence already as it may have been
- * pinned by a session, but the user_struct pointing to it
- * may have been destroyed by setuid */
- sprintf(buf, "_uid.%u", uid);
-
- uid_keyring = find_keyring_by_name(buf, true);
+ /* Get the user keyring. Note that there may be one in existence
+ * already as it may have been pinned by a session, but the user_struct
+ * pointing to it may have been destroyed by setuid.
+ */
+ snprintf(buf, sizeof(buf), "_uid.%u", uid);
+ uid_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
+ &key_type_keyring, buf, false);
+ 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,
+ KEY_ALLOC_UID_KEYRING |
+ KEY_ALLOC_IN_QUOTA,
+ NULL, reg_keyring);
if (IS_ERR(uid_keyring)) {
- uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
- cred, user_keyring_perm,
- KEY_ALLOC_UID_KEYRING |
- KEY_ALLOC_IN_QUOTA,
- NULL, NULL);
- if (IS_ERR(uid_keyring)) {
- ret = PTR_ERR(uid_keyring);
- goto error;
- }
+ ret = PTR_ERR(uid_keyring);
+ goto error;
}
+ } else if (IS_ERR(uid_keyring_r)) {
+ ret = PTR_ERR(uid_keyring_r);
+ goto error;
+ } else {
+ uid_keyring = key_ref_to_ptr(uid_keyring_r);
+ }
- /* get a default session keyring (which might also exist
- * already) */
- sprintf(buf, "_uid_ses.%u", uid);
-
- session_keyring = find_keyring_by_name(buf, true);
+ /* Get a default session keyring (which might also exist already) */
+ snprintf(buf, sizeof(buf), "_uid_ses.%u", uid);
+ session_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
+ &key_type_keyring, buf, false);
+ 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,
+ KEY_ALLOC_UID_KEYRING |
+ KEY_ALLOC_IN_QUOTA,
+ NULL, NULL);
if (IS_ERR(session_keyring)) {
- session_keyring =
- keyring_alloc(buf, user->uid, INVALID_GID,
- cred, user_keyring_perm,
- KEY_ALLOC_UID_KEYRING |
- KEY_ALLOC_IN_QUOTA,
- NULL, NULL);
- if (IS_ERR(session_keyring)) {
- ret = PTR_ERR(session_keyring);
- goto error_release;
- }
-
- /* we install a link from the user session keyring to
- * the user keyring */
- ret = key_link(session_keyring, uid_keyring);
- if (ret < 0)
- goto error_release_both;
+ ret = PTR_ERR(session_keyring);
+ goto error_release;
}
- /* install the keyrings */
- /* paired with READ_ONCE() */
- smp_store_release(&user->uid_keyring, uid_keyring);
- /* paired with READ_ONCE() */
- smp_store_release(&user->session_keyring, session_keyring);
+ /* We install a link from the user session keyring to
+ * the user keyring.
+ */
+ ret = key_link(session_keyring, uid_keyring);
+ if (ret < 0)
+ goto error_release_session;
+
+ /* And only then link the user-session keyring to the
+ * register.
+ */
+ ret = key_link(reg_keyring, session_keyring);
+ if (ret < 0)
+ goto error_release_session;
+ } else if (IS_ERR(session_keyring_r)) {
+ ret = PTR_ERR(session_keyring_r);
+ goto error_release;
+ } else {
+ session_keyring = key_ref_to_ptr(session_keyring_r);
}
- mutex_unlock(&key_user_keyring_mutex);
+ up_write(&user_ns->keyring_sem);
+
+ if (_user_session_keyring)
+ *_user_session_keyring = session_keyring;
+ else
+ key_put(session_keyring);
+ if (_user_keyring)
+ *_user_keyring = uid_keyring;
+ else
+ key_put(uid_keyring);
kleave(" = 0");
return 0;
-error_release_both:
+error_release_session:
key_put(session_keyring);
error_release:
key_put(uid_keyring);
error:
- mutex_unlock(&key_user_keyring_mutex);
+ up_write(&user_ns->keyring_sem);
kleave(" = %d", ret);
return ret;
}
+/*
+ * Get the user session keyring if it exists, but don't create it if it
+ * doesn't.
+ */
+struct key *get_user_session_keyring_rcu(const struct cred *cred)
+{
+ struct key *reg_keyring = READ_ONCE(cred->user_ns->user_keyring_register);
+ key_ref_t session_keyring_r;
+ char buf[20];
+
+ struct keyring_search_context ctx = {
+ .index_key.type = &key_type_keyring,
+ .index_key.description = buf,
+ .cred = cred,
+ .match_data.cmp = key_default_cmp,
+ .match_data.raw_data = buf,
+ .match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
+ .flags = KEYRING_SEARCH_DO_STATE_CHECK,
+ };
+
+ if (!reg_keyring)
+ return NULL;
+
+ ctx.index_key.desc_len = snprintf(buf, sizeof(buf), "_uid_ses.%u",
+ from_kuid(cred->user_ns,
+ cred->user->uid));
+
+ session_keyring_r = keyring_search_rcu(make_key_ref(reg_keyring, true),
+ &ctx);
+ if (IS_ERR(session_keyring_r))
+ return NULL;
+ return key_ref_to_ptr(session_keyring_r);
+}
+
/*
* Install a thread keyring to the given credentials struct if it didn't have
* one already. This is allowed to overrun the quota.
@@ -340,6 +425,7 @@ void key_fsgid_changed(struct cred *new_cred)
*/
key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
{
+ struct key *user_session;
key_ref_t key_ref, ret, err;
const struct cred *cred = ctx->cred;
@@ -415,10 +501,11 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
}
}
/* or search the user-session keyring */
- else if (READ_ONCE(cred->user->session_keyring)) {
- key_ref = keyring_search_rcu(
- make_key_ref(READ_ONCE(cred->user->session_keyring), 1),
- ctx);
+ else if ((user_session = get_user_session_keyring_rcu(cred))) {
+ key_ref = keyring_search_rcu(make_key_ref(user_session, 1),
+ ctx);
+ key_put(user_session);
+
if (!IS_ERR(key_ref))
goto found;
@@ -535,7 +622,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
KEYRING_SEARCH_RECURSE),
};
struct request_key_auth *rka;
- struct key *key;
+ struct key *key, *user_session;
key_ref_t key_ref, skey_ref;
int ret;
@@ -584,20 +671,20 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
if (!ctx.cred->session_keyring) {
/* always install a session keyring upon access if one
* doesn't exist yet */
- ret = install_user_keyrings();
+ ret = look_up_user_keyrings(NULL, &user_session);
if (ret < 0)
goto error;
if (lflags & KEY_LOOKUP_CREATE)
ret = join_session_keyring(NULL);
else
- ret = install_session_keyring(
- ctx.cred->user->session_keyring);
+ ret = install_session_keyring(user_session);
+ key_put(user_session);
if (ret < 0)
goto error;
goto reget_creds;
- } else if (ctx.cred->session_keyring ==
- READ_ONCE(ctx.cred->user->session_keyring) &&
+ } else if (test_bit(KEY_FLAG_UID_KEYRING,
+ &ctx.cred->session_keyring->flags) &&
lflags & KEY_LOOKUP_CREATE) {
ret = join_session_keyring(NULL);
if (ret < 0)
@@ -611,26 +698,16 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
break;
case KEY_SPEC_USER_KEYRING:
- if (!READ_ONCE(ctx.cred->user->uid_keyring)) {
- ret = install_user_keyrings();
- if (ret < 0)
- goto error;
- }
-
- key = ctx.cred->user->uid_keyring;
- __key_get(key);
+ ret = look_up_user_keyrings(&key, NULL);
+ if (ret < 0)
+ goto error;
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_USER_SESSION_KEYRING:
- if (!READ_ONCE(ctx.cred->user->session_keyring)) {
- ret = install_user_keyrings();
- if (ret < 0)
- goto error;
- }
-
- key = ctx.cred->user->session_keyring;
- __key_get(key);
+ ret = look_up_user_keyrings(NULL, &key);
+ if (ret < 0)
+ goto error;
key_ref = make_key_ref(key, 1);
break;
@@ -879,7 +956,7 @@ void key_change_session_keyring(struct callback_head *twork)
*/
static int __init init_root_keyring(void)
{
- return install_user_keyrings();
+ return look_up_user_keyrings(NULL, NULL);
}
late_initcall(init_root_keyring);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 1ffd3803ce29..9201ca96c4df 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -121,7 +121,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
struct request_key_auth *rka = get_request_key_auth(authkey);
const struct cred *cred = current_cred();
key_serial_t prkey, sskey;
- struct key *key = rka->target_key, *keyring, *session;
+ struct key *key = rka->target_key, *keyring, *session, *user_session;
char *argv[9], *envp[3], uid_str[12], gid_str[12];
char key_str[12], keyring_str[3][12];
char desc[20];
@@ -129,9 +129,9 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
kenter("{%d},{%d},%s", key->serial, authkey->serial, rka->op);
- ret = install_user_keyrings();
+ ret = look_up_user_keyrings(NULL, &user_session);
if (ret < 0)
- goto error_alloc;
+ goto error_us;
/* allocate a new session keyring */
sprintf(desc, "_req.%u", key->serial);
@@ -169,7 +169,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
session = cred->session_keyring;
if (!session)
- session = cred->user->session_keyring;
+ session = user_session;
sskey = session->serial;
sprintf(keyring_str[2], "%d", sskey);
@@ -211,6 +211,8 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
key_put(keyring);
error_alloc:
+ key_put(user_session);
+error_us:
complete_request_key(authkey, ret);
kleave(" = %d", ret);
return ret;
@@ -317,13 +319,15 @@ static int construct_get_dest_keyring(struct key **_dest_keyring)
/* fall through */
case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
- dest_keyring =
- key_get(READ_ONCE(cred->user->session_keyring));
+ ret = look_up_user_keyrings(NULL, &dest_keyring);
+ if (ret < 0)
+ return ret;
break;
case KEY_REQKEY_DEFL_USER_KEYRING:
- dest_keyring =
- key_get(READ_ONCE(cred->user->uid_keyring));
+ ret = look_up_user_keyrings(&dest_keyring, NULL);
+ if (ret < 0)
+ return ret;
break;
case KEY_REQKEY_DEFL_GROUP_KEYRING:
^ permalink raw reply related
* [PATCH 4/9] keys: Namespace keyring names [ver #4]
From: David Howells @ 2019-06-19 16:47 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Keyring names are held in a single global list that any process can pick
from by means of keyctl_join_session_keyring (provided the keyring grants
Search permission). This isn't very container friendly, however.
Make the following changes:
(1) Make default session, process and thread keyring names begin with a
'.' instead of '_'.
(2) Keyrings whose names begin with a '.' aren't added to the list. Such
keyrings are system specials.
(3) Replace the global list with per-user_namespace lists. A keyring adds
its name to the list for the user_namespace that it is currently in.
(4) When a user_namespace is deleted, it just removes itself from the
keyring name list.
The global keyring_name_lock is retained for accessing the name lists.
This allows (4) to work.
This can be tested by:
# keyctl newring foo @s
995906392
# unshare -U
$ keyctl show
...
995906392 --alswrv 65534 65534 \_ keyring: foo
...
$ keyctl session foo
Joined session keyring: 935622349
As can be seen, a new session keyring was created.
The capability bit KEYCTL_CAPS1_NS_KEYRING_NAME is set if the kernel is
employing this feature.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric W. Biederman <ebiederm@xmission.com>
---
include/linux/key.h | 2 +
include/linux/user_namespace.h | 5 ++
include/uapi/linux/keyctl.h | 1
kernel/user.c | 3 +
kernel/user_namespace.c | 7 ++-
security/keys/keyctl.c | 3 +
security/keys/keyring.c | 99 +++++++++++++++++-----------------------
7 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index ff102731b3db..ae1177302d70 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -361,6 +361,7 @@ extern void key_set_timeout(struct key *, unsigned);
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
key_perm_t perm);
+extern void key_free_user_ns(struct user_namespace *);
/*
* The permissions required on a key that we're looking up.
@@ -434,6 +435,7 @@ extern void key_init(void);
#define key_fsuid_changed(c) do { } while(0)
#define key_fsgid_changed(c) do { } while(0)
#define key_init() do { } while(0)
+#define key_free_user_ns(ns) do { } while(0)
#endif /* CONFIG_KEYS */
#endif /* __KERNEL__ */
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index d6b74b91096b..90457015fa3f 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -64,6 +64,11 @@ struct user_namespace {
struct ns_common ns;
unsigned long flags;
+#ifdef CONFIG_KEYS
+ /* List of joinable keyrings in this namespace */
+ struct list_head keyring_name_list;
+#endif
+
/* Register of per-UID persistent keyrings for this namespace */
#ifdef CONFIG_PERSISTENT_KEYRINGS
struct key *persistent_keyring_register;
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 551b5814f53e..35b405034674 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -128,5 +128,6 @@ struct keyctl_pkey_params {
#define KEYCTL_CAPS0_INVALIDATE 0x20 /* KEYCTL_INVALIDATE supported */
#define KEYCTL_CAPS0_RESTRICT_KEYRING 0x40 /* KEYCTL_RESTRICT_KEYRING supported */
#define KEYCTL_CAPS0_MOVE 0x80 /* KEYCTL_MOVE supported */
+#define KEYCTL_CAPS1_NS_KEYRING_NAME 0x01 /* Keyring names are per-user_namespace */
#endif /* _LINUX_KEYCTL_H */
diff --git a/kernel/user.c b/kernel/user.c
index 88b834f0eebc..50979fd1b7aa 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -62,6 +62,9 @@ struct user_namespace init_user_ns = {
.ns.ops = &userns_operations,
#endif
.flags = USERNS_INIT_FLAGS,
+#ifdef CONFIG_KEYS
+ .keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
+#endif
#ifdef CONFIG_PERSISTENT_KEYRINGS
.persistent_keyring_register_sem =
__RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 923414a246e9..bda6e890ad88 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -133,6 +133,9 @@ int create_user_ns(struct cred *new)
ns->flags = parent_ns->flags;
mutex_unlock(&userns_state_mutex);
+#ifdef CONFIG_KEYS
+ INIT_LIST_HEAD(&ns->keyring_name_list);
+#endif
#ifdef CONFIG_PERSISTENT_KEYRINGS
init_rwsem(&ns->persistent_keyring_register_sem);
#endif
@@ -196,9 +199,7 @@ static void free_user_ns(struct work_struct *work)
kfree(ns->projid_map.reverse);
}
retire_userns_sysctls(ns);
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- key_put(ns->persistent_keyring_register);
-#endif
+ key_free_user_ns(ns);
ns_free_inum(&ns->ns);
kmem_cache_free(user_ns_cachep, ns);
dec_user_namespaces(ucounts);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 169409b611b0..8a813220f269 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -30,7 +30,7 @@
#define KEY_MAX_DESC_SIZE 4096
-static const unsigned char keyrings_capabilities[1] = {
+static const unsigned char keyrings_capabilities[2] = {
[0] = (KEYCTL_CAPS0_CAPABILITIES |
(IS_ENABLED(CONFIG_PERSISTENT_KEYRINGS) ? KEYCTL_CAPS0_PERSISTENT_KEYRINGS : 0) |
(IS_ENABLED(CONFIG_KEY_DH_OPERATIONS) ? KEYCTL_CAPS0_DIFFIE_HELLMAN : 0) |
@@ -40,6 +40,7 @@ static const unsigned char keyrings_capabilities[1] = {
KEYCTL_CAPS0_RESTRICT_KEYRING |
KEYCTL_CAPS0_MOVE
),
+ [1] = (KEYCTL_CAPS1_NS_KEYRING_NAME),
};
static int key_get_type_from_user(char *type,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 20891cd198f0..fe851292509e 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -16,6 +16,7 @@
#include <linux/security.h>
#include <linux/seq_file.h>
#include <linux/err.h>
+#include <linux/user_namespace.h>
#include <keys/keyring-type.h>
#include <keys/user-type.h>
#include <linux/assoc_array_priv.h>
@@ -28,11 +29,6 @@
*/
#define KEYRING_SEARCH_MAX_DEPTH 6
-/*
- * We keep all named keyrings in a hash to speed looking them up.
- */
-#define KEYRING_NAME_HASH_SIZE (1 << 5)
-
/*
* We mark pointers we pass to the associative array with bit 1 set if
* they're keyrings and clear otherwise.
@@ -55,17 +51,20 @@ static inline void *keyring_key_to_ptr(struct key *key)
return key;
}
-static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE];
static DEFINE_RWLOCK(keyring_name_lock);
-static inline unsigned keyring_hash(const char *desc)
+/*
+ * Clean up the bits of user_namespace that belong to us.
+ */
+void key_free_user_ns(struct user_namespace *ns)
{
- unsigned bucket = 0;
-
- for (; *desc; desc++)
- bucket += (unsigned char)*desc;
+ write_lock(&keyring_name_lock);
+ list_del_init(&ns->keyring_name_list);
+ write_unlock(&keyring_name_lock);
- return bucket & (KEYRING_NAME_HASH_SIZE - 1);
+#ifdef CONFIG_PERSISTENT_KEYRINGS
+ key_put(ns->persistent_keyring_register);
+#endif
}
/*
@@ -104,23 +103,17 @@ static DEFINE_MUTEX(keyring_serialise_link_lock);
/*
* Publish the name of a keyring so that it can be found by name (if it has
- * one).
+ * one and it doesn't begin with a dot).
*/
static void keyring_publish_name(struct key *keyring)
{
- int bucket;
-
- if (keyring->description) {
- bucket = keyring_hash(keyring->description);
+ struct user_namespace *ns = current_user_ns();
+ if (keyring->description &&
+ keyring->description[0] &&
+ keyring->description[0] != '.') {
write_lock(&keyring_name_lock);
-
- if (!keyring_name_hash[bucket].next)
- INIT_LIST_HEAD(&keyring_name_hash[bucket]);
-
- list_add_tail(&keyring->name_link,
- &keyring_name_hash[bucket]);
-
+ list_add_tail(&keyring->name_link, &ns->keyring_name_list);
write_unlock(&keyring_name_lock);
}
}
@@ -1097,50 +1090,44 @@ key_ref_t find_key_to_update(key_ref_t keyring_ref,
*/
struct key *find_keyring_by_name(const char *name, bool uid_keyring)
{
+ struct user_namespace *ns = current_user_ns();
struct key *keyring;
- int bucket;
if (!name)
return ERR_PTR(-EINVAL);
- bucket = keyring_hash(name);
-
read_lock(&keyring_name_lock);
- if (keyring_name_hash[bucket].next) {
- /* search this hash bucket for a keyring with a matching name
- * that's readable and that hasn't been revoked */
- list_for_each_entry(keyring,
- &keyring_name_hash[bucket],
- name_link
- ) {
- if (!kuid_has_mapping(current_user_ns(), keyring->user->uid))
- continue;
-
- if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
- continue;
+ /* Search this hash bucket for a keyring with a matching name that
+ * grants Search permission and that hasn't been revoked
+ */
+ list_for_each_entry(keyring, &ns->keyring_name_list, name_link) {
+ if (!kuid_has_mapping(ns, keyring->user->uid))
+ continue;
- if (strcmp(keyring->description, name) != 0)
- continue;
+ if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
+ continue;
- if (uid_keyring) {
- if (!test_bit(KEY_FLAG_UID_KEYRING,
- &keyring->flags))
- continue;
- } else {
- if (key_permission(make_key_ref(keyring, 0),
- KEY_NEED_SEARCH) < 0)
- continue;
- }
+ if (strcmp(keyring->description, name) != 0)
+ continue;
- /* we've got a match but we might end up racing with
- * key_cleanup() if the keyring is currently 'dead'
- * (ie. it has a zero usage count) */
- if (!refcount_inc_not_zero(&keyring->usage))
+ if (uid_keyring) {
+ if (!test_bit(KEY_FLAG_UID_KEYRING,
+ &keyring->flags))
+ continue;
+ } else {
+ if (key_permission(make_key_ref(keyring, 0),
+ KEY_NEED_SEARCH) < 0)
continue;
- keyring->last_used_at = ktime_get_real_seconds();
- goto out;
}
+
+ /* we've got a match but we might end up racing with
+ * key_cleanup() if the keyring is currently 'dead'
+ * (ie. it has a zero usage count) */
+ if (!refcount_inc_not_zero(&keyring->usage))
+ continue;
+ keyring->last_used_at = ktime_get_real_seconds();
+ goto out;
}
keyring = ERR_PTR(-ENOKEY);
^ permalink raw reply related
* [PATCH 3/9] keys: Add a 'recurse' flag for keyring searches [ver #4]
From: David Howells @ 2019-06-19 16:47 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Add a 'recurse' flag for keyring searches so that the flag can be omitted
and recursion disabled, thereby allowing just the nominated keyring to be
searched and none of the children.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 10 ++++++----
certs/blacklist.c | 2 +-
crypto/asymmetric_keys/asymmetric_type.c | 2 +-
include/linux/key.h | 3 ++-
lib/digsig.c | 2 +-
net/rxrpc/security.c | 2 +-
security/integrity/digsig_asymmetric.c | 4 ++--
security/keys/internal.h | 1 +
security/keys/keyctl.c | 2 +-
security/keys/keyring.c | 12 ++++++++++--
security/keys/proc.c | 3 ++-
security/keys/process_keys.c | 3 ++-
security/keys/request_key.c | 3 ++-
security/keys/request_key_auth.c | 3 ++-
14 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index a0e245f9576f..ae930ae9d590 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1162,11 +1162,13 @@ payload contents" for more information.
key_ref_t keyring_search(key_ref_t keyring_ref,
const struct key_type *type,
- const char *description)
+ const char *description,
+ bool recurse)
- This searches the keyring tree specified for a matching key. Error ENOKEY
- is returned upon failure (use IS_ERR/PTR_ERR to determine). If successful,
- the returned key will need to be released.
+ This searches the specified keyring only (recurse == false) or keyring tree
+ (recurse == true) specified for a matching key. Error ENOKEY is returned
+ upon failure (use IS_ERR/PTR_ERR to determine). If successful, the returned
+ key will need to be released.
The possession attribute from the keyring reference is used to control
access through the permissions mask and is propagated to the returned key
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..181cb7fa9540 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -128,7 +128,7 @@ int is_hash_blacklisted(const u8 *hash, size_t hash_len, const char *type)
*p = 0;
kref = keyring_search(make_key_ref(blacklist_keyring, true),
- &key_type_blacklist, buffer);
+ &key_type_blacklist, buffer, false);
if (!IS_ERR(kref)) {
key_ref_put(kref);
ret = -EKEYREJECTED;
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 69a0788a7de5..084027ef3121 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -87,7 +87,7 @@ struct key *find_asymmetric_key(struct key *keyring,
pr_debug("Look up: \"%s\"\n", req);
ref = keyring_search(make_key_ref(keyring, 1),
- &key_type_asymmetric, req);
+ &key_type_asymmetric, req, true);
if (IS_ERR(ref))
pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref));
kfree(req);
diff --git a/include/linux/key.h b/include/linux/key.h
index fb2debcacea0..ff102731b3db 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -341,7 +341,8 @@ extern int keyring_clear(struct key *keyring);
extern key_ref_t keyring_search(key_ref_t keyring,
struct key_type *type,
- const char *description);
+ const char *description,
+ bool recurse);
extern int keyring_add_key(struct key *keyring,
struct key *key);
diff --git a/lib/digsig.c b/lib/digsig.c
index 3b0a579bdcdf..3782af401c68 100644
--- a/lib/digsig.c
+++ b/lib/digsig.c
@@ -221,7 +221,7 @@ int digsig_verify(struct key *keyring, const char *sig, int siglen,
/* search in specific keyring */
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1UL),
- &key_type_user, name);
+ &key_type_user, name, true);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
diff --git a/net/rxrpc/security.c b/net/rxrpc/security.c
index c4479afe8ae7..2cfc7125bc41 100644
--- a/net/rxrpc/security.c
+++ b/net/rxrpc/security.c
@@ -148,7 +148,7 @@ int rxrpc_init_server_conn_security(struct rxrpc_connection *conn)
/* look through the service's keyring */
kref = keyring_search(make_key_ref(rx->securities, 1UL),
- &key_type_rxrpc_s, kdesc);
+ &key_type_rxrpc_s, kdesc, true);
if (IS_ERR(kref)) {
read_unlock(&local->services_lock);
_leave(" = %ld [search]", PTR_ERR(kref));
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 99080871eb9f..358f614811e8 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -39,7 +39,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
key_ref_t kref;
kref = keyring_search(make_key_ref(key, 1),
- &key_type_asymmetric, name);
+ &key_type_asymmetric, name, true);
if (!IS_ERR(kref)) {
pr_err("Key '%s' is in ima_blacklist_keyring\n", name);
return ERR_PTR(-EKEYREJECTED);
@@ -51,7 +51,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1),
- &key_type_asymmetric, name);
+ &key_type_asymmetric, name, true);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 4305414795ae..aa361299a3ec 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -127,6 +127,7 @@ struct keyring_search_context {
#define KEYRING_SEARCH_NO_CHECK_PERM 0x0008 /* Don't check permissions */
#define KEYRING_SEARCH_DETECT_TOO_DEEP 0x0010 /* Give an error on excessive depth */
#define KEYRING_SEARCH_SKIP_EXPIRED 0x0020 /* Ignore expired keys (intention to replace) */
+#define KEYRING_SEARCH_RECURSE 0x0040 /* Search child keyrings also */
int (*iterator)(const void *object, void *iterator_data);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 9f418e66f067..169409b611b0 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -762,7 +762,7 @@ long keyctl_keyring_search(key_serial_t ringid,
}
/* do the search */
- key_ref = keyring_search(keyring_ref, ktype, description);
+ key_ref = keyring_search(keyring_ref, ktype, description, true);
if (IS_ERR(key_ref)) {
ret = PTR_ERR(key_ref);
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index a5ee3b4d2eb8..20891cd198f0 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -685,6 +685,9 @@ static bool search_nested_keyrings(struct key *keyring,
* Non-keyrings avoid the leftmost branch of the root entirely (root
* slots 1-15).
*/
+ if (!(ctx->flags & KEYRING_SEARCH_RECURSE))
+ goto not_this_keyring;
+
ptr = READ_ONCE(keyring->keys.root);
if (!ptr)
goto not_this_keyring;
@@ -885,13 +888,15 @@ key_ref_t keyring_search_rcu(key_ref_t keyring_ref,
* @keyring: The root of the keyring tree to be searched.
* @type: The type of keyring we want to find.
* @description: The name of the keyring we want to find.
+ * @recurse: True to search the children of @keyring also
*
* As keyring_search_rcu() above, but using the current task's credentials and
* type's default matching function and preferred search method.
*/
key_ref_t keyring_search(key_ref_t keyring,
struct key_type *type,
- const char *description)
+ const char *description,
+ bool recurse)
{
struct keyring_search_context ctx = {
.index_key.type = type,
@@ -906,6 +911,8 @@ key_ref_t keyring_search(key_ref_t keyring,
key_ref_t key;
int ret;
+ if (recurse)
+ ctx.flags |= KEYRING_SEARCH_RECURSE;
if (type->match_preparse) {
ret = type->match_preparse(&ctx.match_data);
if (ret < 0)
@@ -1176,7 +1183,8 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
.flags = (KEYRING_SEARCH_NO_STATE_CHECK |
KEYRING_SEARCH_NO_UPDATE_TIME |
KEYRING_SEARCH_NO_CHECK_PERM |
- KEYRING_SEARCH_DETECT_TOO_DEEP),
+ KEYRING_SEARCH_DETECT_TOO_DEEP |
+ KEYRING_SEARCH_RECURSE),
};
rcu_read_lock();
diff --git a/security/keys/proc.c b/security/keys/proc.c
index f081dceae3b9..b4f5ba56b9cb 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -170,7 +170,8 @@ static int proc_keys_show(struct seq_file *m, void *v)
.match_data.cmp = lookup_user_key_possessed,
.match_data.raw_data = key,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_NO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_NO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
key_ref = make_key_ref(key, 0);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index f8ffb06d0297..b07f768d23dc 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -531,7 +531,8 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
struct keyring_search_context ctx = {
.match_data.cmp = lookup_user_key_possessed,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_NO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_NO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
struct request_key_auth *rka;
struct key *key;
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 36c55ef47b9e..1ffd3803ce29 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -569,7 +569,8 @@ struct key *request_key_and_link(struct key_type *type,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = (KEYRING_SEARCH_DO_STATE_CHECK |
- KEYRING_SEARCH_SKIP_EXPIRED),
+ KEYRING_SEARCH_SKIP_EXPIRED |
+ KEYRING_SEARCH_RECURSE),
};
struct key *key;
key_ref_t key_ref;
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 99ed7a8a273d..f613987e8a63 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -252,7 +252,8 @@ struct key *key_get_instantiation_authkey(key_serial_t target_id)
.match_data.cmp = key_default_cmp,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_DO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_DO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
struct key *authkey;
key_ref_t authkey_ref;
^ permalink raw reply related
* [PATCH 2/9] keys: Cache the hash value to avoid lots of recalculation [ver #4]
From: David Howells @ 2019-06-19 16:46 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Cache the hash of the key's type and description in the index key so that
we're not recalculating it every time we look at a key during a search.
The hash function does a bunch of multiplications, so evading those is
probably worthwhile - especially as this is done for every key examined
during a search.
This also allows the methods used by assoc_array to get chunks of index-key
to be simplified.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/linux/key.h | 3 +++
security/keys/internal.h | 8 +-------
security/keys/key.c | 2 +-
security/keys/keyring.c | 28 ++++++++++++++++++++--------
4 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index 86ccc2d010f6..fb2debcacea0 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -86,6 +86,8 @@ struct keyring_list;
struct keyring_name;
struct keyring_index_key {
+ /* [!] If this structure is altered, the union in struct key must change too! */
+ unsigned long hash; /* Hash value */
union {
struct {
#ifdef __LITTLE_ENDIAN /* Put desc_len at the LSB of x */
@@ -213,6 +215,7 @@ struct key {
union {
struct keyring_index_key index_key;
struct {
+ unsigned long hash;
unsigned long len_desc;
struct key_type *type; /* type of key */
char *description;
diff --git a/security/keys/internal.h b/security/keys/internal.h
index ee71c72fc5f0..4305414795ae 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -89,13 +89,7 @@ extern spinlock_t key_serial_lock;
extern struct mutex key_construction_mutex;
extern wait_queue_head_t request_key_conswq;
-
-static inline void key_set_index_key(struct keyring_index_key *index_key)
-{
- size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
- memcpy(index_key->desc, index_key->description, n);
-}
-
+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);
diff --git a/security/keys/key.c b/security/keys/key.c
index 0a3828f15f57..9d52f2472a09 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -285,12 +285,12 @@ struct key *key_alloc(struct key_type *type, const char *desc,
key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL);
if (!key->index_key.description)
goto no_memory_3;
+ key->index_key.type = type;
key_set_index_key(&key->index_key);
refcount_set(&key->usage, 1);
init_rwsem(&key->sem);
lockdep_set_class(&key->sem, &type->lock_class);
- key->index_key.type = type;
key->user = user;
key->quotalen = quotalen;
key->datalen = type->def_datalen;
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index ebf52077598f..a5ee3b4d2eb8 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -168,7 +168,7 @@ static u64 mult_64x32_and_fold(u64 x, u32 y)
/*
* Hash a key type and description.
*/
-static unsigned long hash_key_type_and_desc(const struct keyring_index_key *index_key)
+static void hash_key_type_and_desc(struct keyring_index_key *index_key)
{
const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP;
const unsigned long fan_mask = ASSOC_ARRAY_FAN_MASK;
@@ -206,10 +206,22 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
* zero for keyrings and non-zero otherwise.
*/
if (index_key->type != &key_type_keyring && (hash & fan_mask) == 0)
- return hash | (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1;
- if (index_key->type == &key_type_keyring && (hash & fan_mask) != 0)
- return (hash + (hash << level_shift)) & ~fan_mask;
- return hash;
+ hash |= (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1;
+ else if (index_key->type == &key_type_keyring && (hash & fan_mask) != 0)
+ hash = (hash + (hash << level_shift)) & ~fan_mask;
+ index_key->hash = hash;
+}
+
+/*
+ * Finalise an index key to include a part of the description actually in the
+ * index key and to add in the hash too.
+ */
+void key_set_index_key(struct keyring_index_key *index_key)
+{
+ size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
+ memcpy(index_key->desc, index_key->description, n);
+
+ hash_key_type_and_desc(index_key);
}
/*
@@ -227,7 +239,7 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
level /= ASSOC_ARRAY_KEY_CHUNK_SIZE;
switch (level) {
case 0:
- return hash_key_type_and_desc(index_key);
+ return index_key->hash;
case 1:
return index_key->x;
case 2:
@@ -280,8 +292,8 @@ static int keyring_diff_objects(const void *object, const void *data)
int level, i;
level = 0;
- seg_a = hash_key_type_and_desc(a);
- seg_b = hash_key_type_and_desc(b);
+ seg_a = a->hash;
+ seg_b = b->hash;
if ((seg_a ^ seg_b) != 0)
goto differ;
level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
^ permalink raw reply related
* [PATCH 1/9] keys: Simplify key description management [ver #4]
From: David Howells @ 2019-06-19 16:46 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096279115.28733.8761881995303698232.stgit@warthog.procyon.org.uk>
Simplify key description management by cramming the word containing the
length with the first few chars of the description also. This simplifies
the code that generates the index-key used by assoc_array. It should speed
up key searching a bit too.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/linux/key.h | 14 ++++++++-
security/keys/internal.h | 6 ++++
security/keys/key.c | 2 +
security/keys/keyring.c | 70 +++++++++++++-------------------------------
security/keys/persistent.c | 1 +
5 files changed, 43 insertions(+), 50 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index 4cd5669184f3..86ccc2d010f6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -86,9 +86,20 @@ struct keyring_list;
struct keyring_name;
struct keyring_index_key {
+ union {
+ struct {
+#ifdef __LITTLE_ENDIAN /* Put desc_len at the LSB of x */
+ u8 desc_len;
+ char desc[sizeof(long) - 1]; /* First few chars of description */
+#else
+ char desc[sizeof(long) - 1]; /* First few chars of description */
+ u8 desc_len;
+#endif
+ };
+ unsigned long x;
+ };
struct key_type *type;
const char *description;
- size_t desc_len;
};
union key_payload {
@@ -202,6 +213,7 @@ struct key {
union {
struct keyring_index_key index_key;
struct {
+ unsigned long len_desc;
struct key_type *type; /* type of key */
char *description;
};
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 3d5c08db74d2..ee71c72fc5f0 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -90,6 +90,12 @@ extern struct mutex key_construction_mutex;
extern wait_queue_head_t request_key_conswq;
+static inline void key_set_index_key(struct keyring_index_key *index_key)
+{
+ size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
+ memcpy(index_key->desc, index_key->description, n);
+}
+
extern struct key_type *key_type_lookup(const char *type);
extern void key_type_put(struct key_type *ktype);
diff --git a/security/keys/key.c b/security/keys/key.c
index e792d65c0af8..0a3828f15f57 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -285,6 +285,7 @@ struct key *key_alloc(struct key_type *type, const char *desc,
key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL);
if (!key->index_key.description)
goto no_memory_3;
+ key_set_index_key(&key->index_key);
refcount_set(&key->usage, 1);
init_rwsem(&key->sem);
@@ -868,6 +869,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
goto error_free_prep;
}
index_key.desc_len = strlen(index_key.description);
+ key_set_index_key(&index_key);
ret = __key_link_lock(keyring, &index_key);
if (ret < 0) {
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index afa6d4024c67..ebf52077598f 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -179,9 +179,9 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
int n, desc_len = index_key->desc_len;
type = (unsigned long)index_key->type;
-
acc = mult_64x32_and_fold(type, desc_len + 13);
acc = mult_64x32_and_fold(acc, 9207);
+
for (;;) {
n = desc_len;
if (n <= 0)
@@ -215,23 +215,13 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
/*
* Build the next index key chunk.
*
- * On 32-bit systems the index key is laid out as:
- *
- * 0 4 5 9...
- * hash desclen typeptr desc[]
- *
- * On 64-bit systems:
- *
- * 0 8 9 17...
- * hash desclen typeptr desc[]
- *
* We return it one word-sized chunk at a time.
*/
static unsigned long keyring_get_key_chunk(const void *data, int level)
{
const struct keyring_index_key *index_key = data;
unsigned long chunk = 0;
- long offset = 0;
+ const u8 *d;
int desc_len = index_key->desc_len, n = sizeof(chunk);
level /= ASSOC_ARRAY_KEY_CHUNK_SIZE;
@@ -239,33 +229,23 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
case 0:
return hash_key_type_and_desc(index_key);
case 1:
- return ((unsigned long)index_key->type << 8) | desc_len;
+ return index_key->x;
case 2:
- if (desc_len == 0)
- return (u8)((unsigned long)index_key->type >>
- (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8));
- n--;
- offset = 1;
- /* fall through */
+ return (unsigned long)index_key->type;
default:
- offset += sizeof(chunk) - 1;
- offset += (level - 3) * sizeof(chunk);
- if (offset >= desc_len)
+ level -= 3;
+ if (desc_len <= sizeof(index_key->desc))
return 0;
- desc_len -= offset;
+
+ d = index_key->description + sizeof(index_key->desc);
+ d += level * sizeof(long);
+ desc_len -= sizeof(index_key->desc);
if (desc_len > n)
desc_len = n;
- offset += desc_len;
do {
chunk <<= 8;
- chunk |= ((u8*)index_key->description)[--offset];
+ chunk |= *d++;
} while (--desc_len > 0);
-
- if (level == 2) {
- chunk <<= 8;
- chunk |= (u8)((unsigned long)index_key->type >>
- (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8));
- }
return chunk;
}
}
@@ -304,39 +284,28 @@ static int keyring_diff_objects(const void *object, const void *data)
seg_b = hash_key_type_and_desc(b);
if ((seg_a ^ seg_b) != 0)
goto differ;
+ level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
/* The number of bits contributed by the hash is controlled by a
* constant in the assoc_array headers. Everything else thereafter we
* can deal with as being machine word-size dependent.
*/
- level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
- seg_a = a->desc_len;
- seg_b = b->desc_len;
+ seg_a = a->x;
+ seg_b = b->x;
if ((seg_a ^ seg_b) != 0)
goto differ;
+ level += sizeof(unsigned long);
/* The next bit may not work on big endian */
- level++;
seg_a = (unsigned long)a->type;
seg_b = (unsigned long)b->type;
if ((seg_a ^ seg_b) != 0)
goto differ;
-
level += sizeof(unsigned long);
- if (a->desc_len == 0)
- goto same;
- i = 0;
- if (((unsigned long)a->description | (unsigned long)b->description) &
- (sizeof(unsigned long) - 1)) {
- do {
- seg_a = *(unsigned long *)(a->description + i);
- seg_b = *(unsigned long *)(b->description + i);
- if ((seg_a ^ seg_b) != 0)
- goto differ_plus_i;
- i += sizeof(unsigned long);
- } while (i < (a->desc_len & (sizeof(unsigned long) - 1)));
- }
+ i = sizeof(a->desc);
+ if (a->desc_len <= i)
+ goto same;
for (; i < a->desc_len; i++) {
seg_a = *(unsigned char *)(a->description + i);
@@ -662,6 +631,9 @@ static bool search_nested_keyrings(struct key *keyring,
BUG_ON((ctx->flags & STATE_CHECKS) == 0 ||
(ctx->flags & STATE_CHECKS) == STATE_CHECKS);
+ if (ctx->index_key.description)
+ key_set_index_key(&ctx->index_key);
+
/* Check to see if this top-level keyring is what we are looking for
* and whether it is valid or not.
*/
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index d0cb5b32eff7..fc29ec59efa7 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -87,6 +87,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
index_key.type = &key_type_keyring;
index_key.description = buf;
index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
+ key_set_index_key(&index_key);
if (ns->persistent_keyring_register) {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
^ permalink raw reply related
* [PATCH 0/9] keys: Namespacing [ver #4]
From: David Howells @ 2019-06-19 16:46 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
Here are some patches to make keys and keyrings more namespace aware.
Firstly some miscellaneous patches to make the process easier:
(1) Simplify key index_key handling so that the word-sized chunks
assoc_array requires don't have to be shifted about, making it easier
to add more bits into the key.
(2) Cache the hash value in the key so that we don't have to calculate on
every key we examine during a search (it involves a bunch of
multiplications).
(3) Allow keying_search() to search non-recursively.
Then the main patches:
(4) Make it so that keyring names are per-user_namespace from the point of
view of KEYCTL_JOIN_SESSION_KEYRING so that they're not accessible
cross-user_namespace.
keyctl_capabilities() shows KEYCTL_CAPS1_NS_KEYRING_NAME for this.
(5) Move the user and user-session keyrings to the user_namespace rather
than the user_struct. This prevents them propagating directly across
user_namespaces boundaries (ie. the KEY_SPEC_* flags will only pick
from the current user_namespace).
(6) Make it possible to include the target namespace in which the key shall
operate in the index_key. This will allow the possibility of multiple
keys with the same description, but different target domains to be held
in the same keyring.
keyctl_capabilities() shows KEYCTL_CAPS1_NS_KEY_TAG for this.
(7) Make it so that keys are implicitly invalidated by removal of a domain
tag, causing them to be garbage collected.
(8) Institute a network namespace domain tag that allows keys to be
differentiated by the network namespace in which they operate. New keys
that are of a type marked 'KEY_TYPE_NET_DOMAIN' are assigned the network
domain in force when they are created.
(9) Make it so that the desired network namespace can be handed down into the
request_key() mechanism. This allows AFS, NFS, etc. to request keys
specific to the network namespace of the superblock.
This also means that the keys in the DNS record cache are thenceforth
namespaced, provided network filesystems pass the appropriate network
namespace down into dns_query().
For DNS, AFS and NFS are good; CIFS and Ceph are not. Other cache
keyrings, such as idmapper keyrings, also need to set the domain tag -
for which they need access to the network namespace of the superblock.
The patches can be found on the following branch:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-namespace
David
---
David Howells (9):
keys: Simplify key description management
keys: Cache the hash value to avoid lots of recalculation
keys: Add a 'recurse' flag for keyring searches
keys: Namespace keyring names
keys: Move the user and user-session keyrings to the user_namespace
keys: Include target namespace in match criteria
keys: Garbage collect keys for which the domain has been removed
keys: Network namespace domain tag
keys: Pass the network namespace into request_key mechanism
Documentation/security/keys/core.rst | 38 +++-
Documentation/security/keys/request-key.rst | 29 ++-
certs/blacklist.c | 2
crypto/asymmetric_keys/asymmetric_type.c | 2
fs/afs/addr_list.c | 4
fs/afs/dynroot.c | 8 +
fs/cifs/dns_resolve.c | 3
fs/nfs/dns_resolve.c | 3
fs/nfs/nfs4idmap.c | 2
include/linux/dns_resolver.h | 3
include/linux/key-type.h | 3
include/linux/key.h | 81 ++++++++
include/linux/sched/user.h | 14 -
include/linux/user_namespace.h | 12 +
include/net/net_namespace.h | 3
include/uapi/linux/keyctl.h | 2
kernel/user.c | 8 -
kernel/user_namespace.c | 9 -
lib/digsig.c | 2
net/ceph/messenger.c | 3
net/core/net_namespace.c | 19 ++
net/dns_resolver/dns_key.c | 1
net/dns_resolver/dns_query.c | 7 +
net/rxrpc/key.c | 6 -
net/rxrpc/security.c | 2
security/integrity/digsig_asymmetric.c | 4
security/keys/gc.c | 2
security/keys/internal.h | 10 +
security/keys/key.c | 5 -
security/keys/keyctl.c | 8 +
security/keys/keyring.c | 263 +++++++++++++++------------
security/keys/persistent.c | 10 +
security/keys/proc.c | 3
security/keys/process_keys.c | 262 +++++++++++++++++----------
security/keys/request_key.c | 62 ++++--
security/keys/request_key_auth.c | 3
36 files changed, 588 insertions(+), 310 deletions(-)
^ permalink raw reply
* Re: [PATCH v2 07/25] net: Prepare UDS for secuirty module stacking
From: Casey Schaufler @ 2019-06-19 16:42 UTC (permalink / raw)
To: Kees Cook
Cc: casey.schaufler, jmorris, linux-security-module, selinux,
john.johansen, penguin-kernel, paul, sds
In-Reply-To: <201906182157.29524DC78@keescook>
On 6/18/2019 9:59 PM, Kees Cook wrote:
> typo in Subject -> "secuirty" -> "security"
>
> On Tue, Jun 18, 2019 at 04:05:33PM -0700, Casey Schaufler wrote:
>> Change the data used in UDS SO_PEERSEC processing from a
>> secid to a more general struct lsmblob. Update the
>> security_socket_getpeersec_dgram() interface to use the
>> lsmblob. There is a small amount of scaffolding code
>> that will come out when the security_secid_to_secctx()
>> code is brought in line with the lsmblob.
> Can you spell this out a little more, "scaffolding code passes slot 1
> unconditionally while the following patches will fix this up when they
> are made aware of lsmblob" etc. (Also, why slot 1 and not slot 0?)
It should be slot 0. An earlier version had special meaning for
slot 0, but that proved unnecessary. This is a bug, as it will break
if you have exactly one of the major LSMs built in.
> -Kees
>
>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>> ---
>> include/linux/security.h | 7 +++++--
>> include/net/af_unix.h | 2 +-
>> include/net/scm.h | 8 +++++---
>> net/ipv4/ip_sockglue.c | 8 +++++---
>> net/unix/af_unix.c | 6 +++---
>> security/security.c | 16 +++++++++++++---
>> 6 files changed, 32 insertions(+), 15 deletions(-)
>>
>> diff --git a/include/linux/security.h b/include/linux/security.h
>> index 89a5391f2441..64f5cc2dd249 100644
>> --- a/include/linux/security.h
>> +++ b/include/linux/security.h
>> @@ -1276,7 +1276,8 @@ int security_socket_shutdown(struct socket *sock, int how);
>> int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb);
>> int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
>> int __user *optlen, unsigned len);
>> -int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid);
>> +int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
>> + struct lsmblob *l);
>> int security_sk_alloc(struct sock *sk, int family, gfp_t priority);
>> void security_sk_free(struct sock *sk);
>> void security_sk_clone(const struct sock *sk, struct sock *newsk);
>> @@ -1414,7 +1415,9 @@ static inline int security_socket_getpeersec_stream(struct socket *sock, char __
>> return -ENOPROTOOPT;
>> }
>>
>> -static inline int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
>> +static inline int security_socket_getpeersec_dgram(struct socket *sock,
>> + struct sk_buff *skb,
>> + struct lsmblob *l)
>> {
>> return -ENOPROTOOPT;
>> }
>> diff --git a/include/net/af_unix.h b/include/net/af_unix.h
>> index 3426d6dacc45..933492c08b8c 100644
>> --- a/include/net/af_unix.h
>> +++ b/include/net/af_unix.h
>> @@ -36,7 +36,7 @@ struct unix_skb_parms {
>> kgid_t gid;
>> struct scm_fp_list *fp; /* Passed files */
>> #ifdef CONFIG_SECURITY_NETWORK
>> - u32 secid; /* Security ID */
>> + struct lsmblob lsmblob; /* Security LSM data */
>> #endif
>> u32 consumed;
>> } __randomize_layout;
>> diff --git a/include/net/scm.h b/include/net/scm.h
>> index 1ce365f4c256..c87a17101c86 100644
>> --- a/include/net/scm.h
>> +++ b/include/net/scm.h
>> @@ -33,7 +33,7 @@ struct scm_cookie {
>> struct scm_fp_list *fp; /* Passed files */
>> struct scm_creds creds; /* Skb credentials */
>> #ifdef CONFIG_SECURITY_NETWORK
>> - u32 secid; /* Passed security ID */
>> + struct lsmblob lsmblob; /* Passed LSM data */
>> #endif
>> };
>>
>> @@ -46,7 +46,7 @@ struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);
>> #ifdef CONFIG_SECURITY_NETWORK
>> static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_cookie *scm)
>> {
>> - security_socket_getpeersec_dgram(sock, NULL, &scm->secid);
>> + security_socket_getpeersec_dgram(sock, NULL, &scm->lsmblob);
>> }
>> #else
>> static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_cookie *scm)
>> @@ -97,7 +97,9 @@ static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct sc
>> int err;
>>
>> if (test_bit(SOCK_PASSSEC, &sock->flags)) {
>> - err = security_secid_to_secctx(scm->secid, &secdata, &seclen);
>> + /* Scaffolding - it has to be element 1 for now */
>> + err = security_secid_to_secctx(scm->lsmblob.secid[1],
>> + &secdata, &seclen);
>>
>> if (!err) {
>> put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, seclen, secdata);
>> diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
>> index 82f341e84fae..fbe2147ee595 100644
>> --- a/net/ipv4/ip_sockglue.c
>> +++ b/net/ipv4/ip_sockglue.c
>> @@ -130,15 +130,17 @@ static void ip_cmsg_recv_checksum(struct msghdr *msg, struct sk_buff *skb,
>>
>> static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb)
>> {
>> + struct lsmblob lb;
>> char *secdata;
>> - u32 seclen, secid;
>> + u32 seclen;
>> int err;
>>
>> - err = security_socket_getpeersec_dgram(NULL, skb, &secid);
>> + err = security_socket_getpeersec_dgram(NULL, skb, &lb);
>> if (err)
>> return;
>>
>> - err = security_secid_to_secctx(secid, &secdata, &seclen);
>> + /* Scaffolding - it has to be element 1 */
>> + err = security_secid_to_secctx(lb.secid[1], &secdata, &seclen);
>> if (err)
>> return;
>>
>> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
>> index ddb838a1b74c..c50a004a1389 100644
>> --- a/net/unix/af_unix.c
>> +++ b/net/unix/af_unix.c
>> @@ -143,17 +143,17 @@ static struct hlist_head *unix_sockets_unbound(void *addr)
>> #ifdef CONFIG_SECURITY_NETWORK
>> static void unix_get_secdata(struct scm_cookie *scm, struct sk_buff *skb)
>> {
>> - UNIXCB(skb).secid = scm->secid;
>> + UNIXCB(skb).lsmblob = scm->lsmblob;
>> }
>>
>> static inline void unix_set_secdata(struct scm_cookie *scm, struct sk_buff *skb)
>> {
>> - scm->secid = UNIXCB(skb).secid;
>> + scm->lsmblob = UNIXCB(skb).lsmblob;
>> }
>>
>> static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)
>> {
>> - return (scm->secid == UNIXCB(skb).secid);
>> + return lsmblob_equal(&scm->lsmblob, &(UNIXCB(skb).lsmblob));
>> }
>> #else
>> static inline void unix_get_secdata(struct scm_cookie *scm, struct sk_buff *skb)
>> diff --git a/security/security.c b/security/security.c
>> index 4296cd2ca508..5ed818699e15 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -2132,10 +2132,20 @@ int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
>> optval, optlen, len);
>> }
>>
>> -int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
>> +int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
>> + struct lsmblob *l)
>> {
>> - return call_int_hook(socket_getpeersec_dgram, -ENOPROTOOPT, sock,
>> - skb, secid);
>> + struct security_hook_list *hp;
>> + int rc = -ENOPROTOOPT;
>> +
>> + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_dgram,
>> + list) {
>> + rc = hp->hook.socket_getpeersec_dgram(sock, skb,
>> + &l->secid[hp->slot]);
>> + if (rc != 0)
>> + break;
>> + }
>> + return rc;
>> }
>> EXPORT_SYMBOL(security_socket_getpeersec_dgram);
>>
>> --
>> 2.20.1
>>
^ permalink raw reply
* Re: [PATCH v2 04/25] LSM: Create and manage the lsmblob data structure.
From: Casey Schaufler @ 2019-06-19 16:34 UTC (permalink / raw)
To: Kees Cook
Cc: casey.schaufler, jmorris, linux-security-module, selinux,
john.johansen, penguin-kernel, paul, sds, casey
In-Reply-To: <201906182147.0A592CBB62@keescook>
On 6/18/2019 9:52 PM, Kees Cook wrote:
> On Tue, Jun 18, 2019 at 04:05:30PM -0700, Casey Schaufler wrote:
>> When more than one security module is exporting data to
>> audit and networking sub-systems a single 32 bit integer
>> is no longer sufficient to represent the data. Add a
>> structure to be used instead.
>>
>> The lsmblob structure is currently an array of
>> u32 "secids". There is an entry for each of the
>> security modules built into the system that would
>> use secids if active. The system assigns the module
>> a "slot" when it registers hooks. If modules are
>> compiled in but not registered there will be unused
>> slots.
>>
>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>> ---
>> include/linux/lsm_hooks.h | 1 +
>> include/linux/security.h | 62 +++++++++++++++++++++++++++++++++++++++
>> security/security.c | 31 ++++++++++++++++++++
>> 3 files changed, 94 insertions(+)
>>
>> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
>> index 3fe39abccc8f..4d1ddf1a2aa6 100644
>> --- a/include/linux/lsm_hooks.h
>> +++ b/include/linux/lsm_hooks.h
>> @@ -2038,6 +2038,7 @@ struct security_hook_list {
>> struct hlist_head *head;
>> union security_list_options hook;
>> char *lsm;
>> + int slot;
>> } __randomize_layout;
> Hm, this feels redundant (as does the existing "char *lsm") now that we
> have lsm_info. The place for assigned-at-init value is blob_sizes, which
> hangs off of lsm_info (as does the LSM char *)...
Hm, is right. If the "char *lsm" pointer were replaced with a
"struct lsm_info *lsm_info" pointer, and "int slot" added to
lsm_info it would be a touch cleaner. A little bit of gimmickry
might be required for initialization, but maybe not. It's
worth looking into.
>>
>> /*
>> diff --git a/include/linux/security.h b/include/linux/security.h
>> index 49f2685324b0..28d074866895 100644
>> --- a/include/linux/security.h
>> +++ b/include/linux/security.h
>> @@ -76,6 +76,68 @@ enum lsm_event {
>> LSM_POLICY_CHANGE,
>> };
>>
>> +/*
>> + * Data exported by the security modules
>> + */
>> +#define LSMDATA_ENTRIES ( \
> LSMBLOB_ENTRIES?
That would be about 7% better.
>> + (IS_ENABLED(CONFIG_SECURITY_SELINUX) ? 1 : 0) + \
>> + (IS_ENABLED(CONFIG_SECURITY_SMACK) ? 1 : 0) + \
>> + (IS_ENABLED(CONFIG_SECURITY_APPARMOR) ? 1 : 0) )
>> +
>> +struct lsmblob {
>> + u32 secid[LSMDATA_ENTRIES];
>> +};
> Cool; I like this auto-sizing.
I figured this was either clever or hideous, but
hadn't fully decided which.
>> +
>> +#define LSMDATA_INVALID -1
>> +
>> +/**
>> + * lsmblob_init - initialize an lsmblob structure.
>> + * @l: Pointer to the data to initialize
>> + * @secid: The initial secid value
>> + *
>> + * Set all secid for all modules to the specified value.
>> + */
>> +static inline void lsmblob_init(struct lsmblob *l, u32 secid)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < LSMDATA_ENTRIES; i++)
>> + l->secid[i] = secid;
> For all these LSMDATA_ENTRIES, I prefer ARRAY_SIZE(l->secid), but
> *shrug*
I suppose, although you're adding compile time, and the
definition of LSMDATA_ENTRIES is just above.
>> +}
>> +
>> +/**
>> + * lsmblob_is_set - report if there is an value in the lsmblob
>> + * @l: Pointer to the exported LSM data
>> + *
>> + * Returns true if there is a secid set, false otherwise
>> + */
>> +static inline bool lsmblob_is_set(struct lsmblob *l)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < LSMDATA_ENTRIES; i++)
>> + if (l->secid[i] != 0)
>> + return true;
>> + return false;
>> +}
>> +
>> +/**
>> + * lsmblob_equal - report if the two lsmblob's are equal
>> + * @l: Pointer to one LSM data
>> + * @m: Pointer to the other LSM data
>> + *
>> + * Returns true if all entries in the two are equal, false otherwise
>> + */
>> +static inline bool lsmblob_equal(struct lsmblob *l, struct lsmblob *m)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < LSMDATA_ENTRIES; i++)
>> + if (l->secid[i] != m->secid[i])
>> + return false;
>> + return true;
>> +}
>> +
>> /* These functions are in security/commoncap.c */
>> extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
>> int cap, unsigned int opts);
>> diff --git a/security/security.c b/security/security.c
>> index d05f00a40e82..5aa3c052d702 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -317,6 +317,7 @@ static void __init ordered_lsm_init(void)
>> init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
>> init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
>> init_debug("task blob size = %d\n", blob_sizes.lbs_task);
>> + init_debug("lsmblob size = %lu\n", sizeof(struct lsmblob));
>>
>> /*
>> * Create any kmem_caches needed for blobs
>> @@ -420,6 +421,11 @@ static int lsm_append(char *new, char **result)
>> return 0;
>> }
>>
>> +/*
>> + * Current index to use while initializing the lsmblob secid list.
>> + */
>> +static int lsm_slot __initdata;
>> +
>> /**
>> * security_add_hooks - Add a modules hooks to the hook lists.
>> * @hooks: the hooks to add
>> @@ -427,15 +433,40 @@ static int lsm_append(char *new, char **result)
>> * @lsm: the name of the security module
>> *
>> * Each LSM has to register its hooks with the infrastructure.
>> + * If the LSM is using hooks that export secids allocate a slot
>> + * for it in the lsmblob.
>> */
>> void __init security_add_hooks(struct security_hook_list *hooks, int count,
>> char *lsm)
>> {
>> + int slot = LSMDATA_INVALID;
>> int i;
>>
>> for (i = 0; i < count; i++) {
>> hooks[i].lsm = lsm;
>> hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
>> + /*
>> + * If this is one of the hooks that uses a secid
>> + * note it so that a slot can in allocated for the
>> + * secid in the lsmblob structure.
>> + */
>> + if (hooks[i].head == &security_hook_heads.audit_rule_match ||
>> + hooks[i].head == &security_hook_heads.kernel_act_as ||
>> + hooks[i].head ==
>> + &security_hook_heads.socket_getpeersec_dgram ||
>> + hooks[i].head == &security_hook_heads.secctx_to_secid ||
>> + hooks[i].head == &security_hook_heads.secid_to_secctx ||
>> + hooks[i].head == &security_hook_heads.ipc_getsecid ||
>> + hooks[i].head == &security_hook_heads.task_getsecid ||
>> + hooks[i].head == &security_hook_heads.inode_getsecid ||
>> + hooks[i].head == &security_hook_heads.cred_getsecid) {
>> + if (slot == LSMDATA_INVALID) {
>> + slot = lsm_slot++;
> This needs to sanity check lsm_slot against lsmblob secids array size,
> just we we catch cases cleanly if an LSM adds a hook but doesn't add
> itself to the LSMDATA_ENTRIES macro.
Point, and easy enough.
>> + init_debug("%s assigned lsmblob slot %d\n",
>> + hooks[i].lsm, slot);
>> + }
>> + }
>> + hooks[i].slot = slot;
>> }
>> if (lsm_append(lsm, &lsm_names) < 0)
>> panic("%s - Cannot get early memory.\n", __func__);
>> --
>> 2.20.1
>>
^ permalink raw reply
* Re: [PATCH 0/9] keys: Namespacing [ver #4]
From: David Howells @ 2019-06-19 16:09 UTC (permalink / raw)
Cc: dhowells, ebiederm, keyrings, linux-cifs, linux-nfs, netdev,
linux-afs, dwalsh, vgoyal, linux-security-module, linux-fsdevel,
linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Apologies... A couple of the patches don't compile because I forgot to commit
a change:-(
Will repush and repost.
David
^ permalink raw reply
* [PATCH 5/9] keys: Move the user and user-session keyrings to the user_namespace [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Move the user and user-session keyrings to the user_namespace struct rather
than pinning them from the user_struct struct. This prevents these
keyrings from propagating across user-namespaces boundaries with regard to
the KEY_SPEC_* flags, thereby making them more useful in a containerised
environment.
The issue is that a single user_struct may be represent UIDs in several
different namespaces.
The way the patch does this is by attaching a 'register keyring' in each
user_namespace and then sticking the user and user-session keyrings into
that. It can then be searched to retrieve them.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Jann Horn <jannh@google.com>
---
include/linux/sched/user.h | 14 --
include/linux/user_namespace.h | 9 +
kernel/user.c | 7 -
kernel/user_namespace.c | 4 -
security/keys/internal.h | 3
security/keys/keyring.c | 1
security/keys/persistent.c | 8 +
security/keys/process_keys.c | 259 ++++++++++++++++++++++++++--------------
security/keys/request_key.c | 20 ++-
9 files changed, 196 insertions(+), 129 deletions(-)
diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h
index 468d2565a9fe..917d88edb7b9 100644
--- a/include/linux/sched/user.h
+++ b/include/linux/sched/user.h
@@ -7,8 +7,6 @@
#include <linux/refcount.h>
#include <linux/ratelimit.h>
-struct key;
-
/*
* Some day this will be a full-fledged user tracking system..
*/
@@ -30,18 +28,6 @@ struct user_struct {
unsigned long unix_inflight; /* How many files in flight in unix sockets */
atomic_long_t pipe_bufs; /* how many pages are allocated in pipe buffers */
-#ifdef CONFIG_KEYS
- /*
- * These pointers can only change from NULL to a non-NULL value once.
- * Writes are protected by key_user_keyring_mutex.
- * Unlocked readers should use READ_ONCE() unless they know that
- * install_user_keyrings() has been called successfully (which sets
- * these members to non-NULL values, preventing further modifications).
- */
- struct key *uid_keyring; /* UID specific keyring */
- struct key *session_keyring; /* UID's default session keyring */
-#endif
-
/* Hash table maintenance information */
struct hlist_node uidhash_node;
kuid_t uid;
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 90457015fa3f..fb9f4f799554 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -65,14 +65,19 @@ struct user_namespace {
unsigned long flags;
#ifdef CONFIG_KEYS
- /* List of joinable keyrings in this namespace */
+ /* List of joinable keyrings in this namespace. Modification access of
+ * these pointers is controlled by keyring_sem. Once
+ * user_keyring_register is set, it won't be changed, so it can be
+ * accessed directly with READ_ONCE().
+ */
struct list_head keyring_name_list;
+ struct key *user_keyring_register;
+ struct rw_semaphore keyring_sem;
#endif
/* Register of per-UID persistent keyrings for this namespace */
#ifdef CONFIG_PERSISTENT_KEYRINGS
struct key *persistent_keyring_register;
- struct rw_semaphore persistent_keyring_register_sem;
#endif
struct work_struct work;
#ifdef CONFIG_SYSCTL
diff --git a/kernel/user.c b/kernel/user.c
index 50979fd1b7aa..f8519b62cf9a 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -64,10 +64,7 @@ struct user_namespace init_user_ns = {
.flags = USERNS_INIT_FLAGS,
#ifdef CONFIG_KEYS
.keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
-#endif
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- .persistent_keyring_register_sem =
- __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
+ .keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem),
#endif
};
EXPORT_SYMBOL_GPL(init_user_ns);
@@ -143,8 +140,6 @@ static void free_user(struct user_struct *up, unsigned long flags)
{
uid_hash_remove(up);
spin_unlock_irqrestore(&uidhash_lock, flags);
- key_put(up->uid_keyring);
- key_put(up->session_keyring);
kmem_cache_free(uid_cachep, up);
}
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index bda6e890ad88..c87c2ecc7085 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -135,9 +135,7 @@ int create_user_ns(struct cred *new)
#ifdef CONFIG_KEYS
INIT_LIST_HEAD(&ns->keyring_name_list);
-#endif
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- init_rwsem(&ns->persistent_keyring_register_sem);
+ init_rwsem(&ns->keyring_sem);
#endif
ret = -ENOMEM;
if (!setup_userns_sysctls(ns))
diff --git a/security/keys/internal.h b/security/keys/internal.h
index aa361299a3ec..d3a9439e2386 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -148,7 +148,8 @@ extern key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx)
extern struct key *find_keyring_by_name(const char *name, bool uid_keyring);
-extern int install_user_keyrings(void);
+extern int look_up_user_keyrings(struct key **, struct key **);
+extern struct key *get_user_session_keyring_rcu(const struct cred *);
extern int install_thread_keyring_to_cred(struct cred *);
extern int install_process_keyring_to_cred(struct cred *);
extern int install_session_keyring_to_cred(struct cred *, struct key *);
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index fe851292509e..3663e5168583 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -62,6 +62,7 @@ void key_free_user_ns(struct user_namespace *ns)
list_del_init(&ns->keyring_name_list);
write_unlock(&keyring_name_lock);
+ key_put(ns->user_keyring_register);
#ifdef CONFIG_PERSISTENT_KEYRINGS
key_put(ns->persistent_keyring_register);
#endif
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index fc29ec59efa7..90303fe4a394 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -91,9 +91,9 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
if (ns->persistent_keyring_register) {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
- down_read(&ns->persistent_keyring_register_sem);
+ down_read(&ns->keyring_sem);
persistent_ref = find_key_to_update(reg_ref, &index_key);
- up_read(&ns->persistent_keyring_register_sem);
+ up_read(&ns->keyring_sem);
if (persistent_ref)
goto found;
@@ -102,9 +102,9 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
/* It wasn't in the register, so we'll need to create it. We might
* also need to create the register.
*/
- down_write(&ns->persistent_keyring_register_sem);
+ down_write(&ns->keyring_sem);
persistent_ref = key_create_persistent(ns, uid, &index_key);
- up_write(&ns->persistent_keyring_register_sem);
+ up_write(&ns->keyring_sem);
if (!IS_ERR(persistent_ref))
goto found;
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index b07f768d23dc..f74d64215942 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -19,15 +19,13 @@
#include <linux/security.h>
#include <linux/user_namespace.h>
#include <linux/uaccess.h>
+#include <linux/init_task.h>
#include <keys/request_key_auth-type.h>
#include "internal.h"
/* Session keyring create vs join semaphore */
static DEFINE_MUTEX(key_session_mutex);
-/* User keyring creation semaphore */
-static DEFINE_MUTEX(key_user_keyring_mutex);
-
/* The root user's tracking struct */
struct key_user root_key_user = {
.usage = REFCOUNT_INIT(3),
@@ -39,98 +37,185 @@ struct key_user root_key_user = {
};
/*
- * Install the user and user session keyrings for the current process's UID.
+ * Get or create a user register keyring.
+ */
+static struct key *get_user_register(struct user_namespace *user_ns)
+{
+ struct key *reg_keyring = READ_ONCE(user_ns->user_keyring_register);
+
+ if (reg_keyring)
+ return reg_keyring;
+
+ down_write(&user_ns->keyring_sem);
+
+ /* Make sure there's a register keyring. It gets owned by the
+ * user_namespace's owner.
+ */
+ reg_keyring = user_ns->user_keyring_register;
+ 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);
+ if (!IS_ERR(reg_keyring))
+ smp_store_release(&user_ns->user_keyring_register,
+ reg_keyring);
+ }
+
+ up_write(&user_ns->keyring_sem);
+
+ /* We don't return a ref since the keyring is pinned by the user_ns */
+ return reg_keyring;
+}
+
+/*
+ * Look up the user and user session keyrings for the current process's UID,
+ * creating them if they don't exist.
*/
-int install_user_keyrings(void)
+int look_up_user_keyrings(struct key **_user_keyring,
+ struct key **_user_session_keyring)
{
- struct user_struct *user;
- const struct cred *cred;
- struct key *uid_keyring, *session_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;
- 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);
- kenter("%p{%u}", user, uid);
+ kenter("%u", uid);
- if (READ_ONCE(user->uid_keyring) && READ_ONCE(user->session_keyring)) {
- kleave(" = 0 [exist]");
- return 0;
- }
+ reg_keyring = get_user_register(user_ns);
+ if (IS_ERR(reg_keyring))
+ return PTR_ERR(reg_keyring);
- mutex_lock(&key_user_keyring_mutex);
+ down_write(&user_ns->keyring_sem);
ret = 0;
- if (!user->uid_keyring) {
- /* get the UID-specific keyring
- * - there may be one in existence already as it may have been
- * pinned by a session, but the user_struct pointing to it
- * may have been destroyed by setuid */
- sprintf(buf, "_uid.%u", uid);
-
- uid_keyring = find_keyring_by_name(buf, true);
+ /* Get the user keyring. Note that there may be one in existence
+ * already as it may have been pinned by a session, but the user_struct
+ * pointing to it may have been destroyed by setuid.
+ */
+ snprintf(buf, sizeof(buf), "_uid.%u", uid);
+ uid_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
+ &key_type_keyring, buf, false);
+ 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,
+ KEY_ALLOC_UID_KEYRING |
+ KEY_ALLOC_IN_QUOTA,
+ NULL, reg_keyring);
if (IS_ERR(uid_keyring)) {
- uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
- cred, user_keyring_perm,
- KEY_ALLOC_UID_KEYRING |
- KEY_ALLOC_IN_QUOTA,
- NULL, NULL);
- if (IS_ERR(uid_keyring)) {
- ret = PTR_ERR(uid_keyring);
- goto error;
- }
+ ret = PTR_ERR(uid_keyring);
+ goto error;
}
+ } else if (IS_ERR(uid_keyring_r)) {
+ ret = PTR_ERR(uid_keyring_r);
+ goto error;
+ } else {
+ uid_keyring = key_ref_to_ptr(uid_keyring_r);
+ }
- /* get a default session keyring (which might also exist
- * already) */
- sprintf(buf, "_uid_ses.%u", uid);
-
- session_keyring = find_keyring_by_name(buf, true);
+ /* Get a default session keyring (which might also exist already) */
+ snprintf(buf, sizeof(buf), "_uid_ses.%u", uid);
+ session_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
+ &key_type_keyring, buf, false);
+ 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,
+ KEY_ALLOC_UID_KEYRING |
+ KEY_ALLOC_IN_QUOTA,
+ NULL, NULL);
if (IS_ERR(session_keyring)) {
- session_keyring =
- keyring_alloc(buf, user->uid, INVALID_GID,
- cred, user_keyring_perm,
- KEY_ALLOC_UID_KEYRING |
- KEY_ALLOC_IN_QUOTA,
- NULL, NULL);
- if (IS_ERR(session_keyring)) {
- ret = PTR_ERR(session_keyring);
- goto error_release;
- }
-
- /* we install a link from the user session keyring to
- * the user keyring */
- ret = key_link(session_keyring, uid_keyring);
- if (ret < 0)
- goto error_release_both;
+ ret = PTR_ERR(session_keyring);
+ goto error_release;
}
- /* install the keyrings */
- /* paired with READ_ONCE() */
- smp_store_release(&user->uid_keyring, uid_keyring);
- /* paired with READ_ONCE() */
- smp_store_release(&user->session_keyring, session_keyring);
+ /* We install a link from the user session keyring to
+ * the user keyring.
+ */
+ ret = key_link(session_keyring, uid_keyring);
+ if (ret < 0)
+ goto error_release_session;
+
+ /* And only then link the user-session keyring to the
+ * register.
+ */
+ ret = key_link(reg_keyring, session_keyring);
+ if (ret < 0)
+ goto error_release_session;
+ } else if (IS_ERR(session_keyring_r)) {
+ ret = PTR_ERR(session_keyring_r);
+ goto error_release;
+ } else {
+ session_keyring = key_ref_to_ptr(session_keyring_r);
}
- mutex_unlock(&key_user_keyring_mutex);
+ up_write(&user_ns->keyring_sem);
+
+ if (_user_session_keyring)
+ *_user_session_keyring = session_keyring;
+ else
+ key_put(session_keyring);
+ if (_user_keyring)
+ *_user_keyring = uid_keyring;
+ else
+ key_put(uid_keyring);
kleave(" = 0");
return 0;
-error_release_both:
+error_release_session:
key_put(session_keyring);
error_release:
key_put(uid_keyring);
error:
- mutex_unlock(&key_user_keyring_mutex);
+ up_write(&user_ns->keyring_sem);
kleave(" = %d", ret);
return ret;
}
+/*
+ * Get the user session keyring if it exists, but don't create it if it
+ * doesn't.
+ */
+struct key *get_user_session_keyring_rcu(const struct cred *cred)
+{
+ struct key *reg_keyring = READ_ONCE(cred->user_ns->user_keyring_register);
+ key_ref_t session_keyring_r;
+ char buf[20];
+
+ struct keyring_search_context ctx = {
+ .index_key.type = &key_type_keyring,
+ .index_key.description = buf,
+ .cred = cred,
+ .match_data.cmp = key_default_cmp,
+ .match_data.raw_data = buf,
+ .match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
+ .flags = KEYRING_SEARCH_DO_STATE_CHECK,
+ };
+
+ if (!reg_keyring)
+ return NULL;
+
+ ctx.index_key.desc_len = snprintf(buf, sizeof(buf), "_uid_ses.%u",
+ from_kuid(cred->user_ns,
+ cred->user->uid));
+
+ session_keyring_r = keyring_search_rcu(make_key_ref(reg_keyring, true),
+ &ctx);
+ if (IS_ERR(session_keyring_r))
+ return NULL;
+ return key_ref_to_ptr(session_keyring_r);
+}
+
/*
* Install a thread keyring to the given credentials struct if it didn't have
* one already. This is allowed to overrun the quota.
@@ -340,6 +425,7 @@ void key_fsgid_changed(struct cred *new_cred)
*/
key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
{
+ struct key *user_session;
key_ref_t key_ref, ret, err;
const struct cred *cred = ctx->cred;
@@ -415,10 +501,11 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
}
}
/* or search the user-session keyring */
- else if (READ_ONCE(cred->user->session_keyring)) {
- key_ref = keyring_search_rcu(
- make_key_ref(READ_ONCE(cred->user->session_keyring), 1),
- ctx);
+ else if ((user_session = get_user_session_keyring_rcu(cred))) {
+ key_ref = keyring_search_rcu(make_key_ref(user_session, 1),
+ ctx);
+ key_put(user_session);
+
if (!IS_ERR(key_ref))
goto found;
@@ -535,7 +622,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
KEYRING_SEARCH_RECURSE),
};
struct request_key_auth *rka;
- struct key *key;
+ struct key *key, *user_session;
key_ref_t key_ref, skey_ref;
int ret;
@@ -584,20 +671,20 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
if (!ctx.cred->session_keyring) {
/* always install a session keyring upon access if one
* doesn't exist yet */
- ret = install_user_keyrings();
+ ret = look_up_user_keyrings(NULL, &user_session);
if (ret < 0)
goto error;
if (lflags & KEY_LOOKUP_CREATE)
ret = join_session_keyring(NULL);
else
- ret = install_session_keyring(
- ctx.cred->user->session_keyring);
+ ret = install_session_keyring(user_session);
+ key_put(user_session);
if (ret < 0)
goto error;
goto reget_creds;
- } else if (ctx.cred->session_keyring ==
- READ_ONCE(ctx.cred->user->session_keyring) &&
+ } else if (test_bit(KEY_FLAG_UID_KEYRING,
+ &ctx.cred->session_keyring->flags) &&
lflags & KEY_LOOKUP_CREATE) {
ret = join_session_keyring(NULL);
if (ret < 0)
@@ -611,26 +698,16 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
break;
case KEY_SPEC_USER_KEYRING:
- if (!READ_ONCE(ctx.cred->user->uid_keyring)) {
- ret = install_user_keyrings();
- if (ret < 0)
- goto error;
- }
-
- key = ctx.cred->user->uid_keyring;
- __key_get(key);
+ ret = look_up_user_keyrings(&key, NULL);
+ if (ret < 0)
+ goto error;
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_USER_SESSION_KEYRING:
- if (!READ_ONCE(ctx.cred->user->session_keyring)) {
- ret = install_user_keyrings();
- if (ret < 0)
- goto error;
- }
-
- key = ctx.cred->user->session_keyring;
- __key_get(key);
+ ret = look_up_user_keyrings(NULL, &key);
+ if (ret < 0)
+ goto error;
key_ref = make_key_ref(key, 1);
break;
@@ -879,7 +956,7 @@ void key_change_session_keyring(struct callback_head *twork)
*/
static int __init init_root_keyring(void)
{
- return install_user_keyrings();
+ return look_up_user_keyrings(NULL, NULL);
}
late_initcall(init_root_keyring);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 1ffd3803ce29..9201ca96c4df 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -121,7 +121,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
struct request_key_auth *rka = get_request_key_auth(authkey);
const struct cred *cred = current_cred();
key_serial_t prkey, sskey;
- struct key *key = rka->target_key, *keyring, *session;
+ struct key *key = rka->target_key, *keyring, *session, *user_session;
char *argv[9], *envp[3], uid_str[12], gid_str[12];
char key_str[12], keyring_str[3][12];
char desc[20];
@@ -129,9 +129,9 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
kenter("{%d},{%d},%s", key->serial, authkey->serial, rka->op);
- ret = install_user_keyrings();
+ ret = look_up_user_keyrings(NULL, &user_session);
if (ret < 0)
- goto error_alloc;
+ goto error_us;
/* allocate a new session keyring */
sprintf(desc, "_req.%u", key->serial);
@@ -169,7 +169,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
session = cred->session_keyring;
if (!session)
- session = cred->user->session_keyring;
+ session = user_session;
sskey = session->serial;
sprintf(keyring_str[2], "%d", sskey);
@@ -211,6 +211,8 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
key_put(keyring);
error_alloc:
+ key_put(user_session);
+error_us:
complete_request_key(authkey, ret);
kleave(" = %d", ret);
return ret;
@@ -317,13 +319,15 @@ static int construct_get_dest_keyring(struct key **_dest_keyring)
/* fall through */
case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
- dest_keyring =
- key_get(READ_ONCE(cred->user->session_keyring));
+ ret = look_up_user_keyrings(NULL, &dest_keyring);
+ if (ret < 0)
+ return ret;
break;
case KEY_REQKEY_DEFL_USER_KEYRING:
- dest_keyring =
- key_get(READ_ONCE(cred->user->uid_keyring));
+ ret = look_up_user_keyrings(&dest_keyring, NULL);
+ if (ret < 0)
+ return ret;
break;
case KEY_REQKEY_DEFL_GROUP_KEYRING:
^ permalink raw reply related
* [PATCH 4/9] keys: Namespace keyring names [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Keyring names are held in a single global list that any process can pick
from by means of keyctl_join_session_keyring (provided the keyring grants
Search permission). This isn't very container friendly, however.
Make the following changes:
(1) Make default session, process and thread keyring names begin with a
'.' instead of '_'.
(2) Keyrings whose names begin with a '.' aren't added to the list. Such
keyrings are system specials.
(3) Replace the global list with per-user_namespace lists. A keyring adds
its name to the list for the user_namespace that it is currently in.
(4) When a user_namespace is deleted, it just removes itself from the
keyring name list.
The global keyring_name_lock is retained for accessing the name lists.
This allows (4) to work.
This can be tested by:
# keyctl newring foo @s
995906392
# unshare -U
$ keyctl show
...
995906392 --alswrv 65534 65534 \_ keyring: foo
...
$ keyctl session foo
Joined session keyring: 935622349
As can be seen, a new session keyring was created.
The capability bit KEYCTL_CAPS1_NS_KEYRING_NAME is set if the kernel is
employing this feature.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric W. Biederman <ebiederm@xmission.com>
---
include/linux/key.h | 2 +
include/linux/user_namespace.h | 5 ++
include/uapi/linux/keyctl.h | 1
kernel/user.c | 3 +
kernel/user_namespace.c | 7 ++-
security/keys/keyctl.c | 1
security/keys/keyring.c | 99 +++++++++++++++++-----------------------
7 files changed, 59 insertions(+), 59 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index ff102731b3db..ae1177302d70 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -361,6 +361,7 @@ extern void key_set_timeout(struct key *, unsigned);
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
key_perm_t perm);
+extern void key_free_user_ns(struct user_namespace *);
/*
* The permissions required on a key that we're looking up.
@@ -434,6 +435,7 @@ extern void key_init(void);
#define key_fsuid_changed(c) do { } while(0)
#define key_fsgid_changed(c) do { } while(0)
#define key_init() do { } while(0)
+#define key_free_user_ns(ns) do { } while(0)
#endif /* CONFIG_KEYS */
#endif /* __KERNEL__ */
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index d6b74b91096b..90457015fa3f 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -64,6 +64,11 @@ struct user_namespace {
struct ns_common ns;
unsigned long flags;
+#ifdef CONFIG_KEYS
+ /* List of joinable keyrings in this namespace */
+ struct list_head keyring_name_list;
+#endif
+
/* Register of per-UID persistent keyrings for this namespace */
#ifdef CONFIG_PERSISTENT_KEYRINGS
struct key *persistent_keyring_register;
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 551b5814f53e..35b405034674 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -128,5 +128,6 @@ struct keyctl_pkey_params {
#define KEYCTL_CAPS0_INVALIDATE 0x20 /* KEYCTL_INVALIDATE supported */
#define KEYCTL_CAPS0_RESTRICT_KEYRING 0x40 /* KEYCTL_RESTRICT_KEYRING supported */
#define KEYCTL_CAPS0_MOVE 0x80 /* KEYCTL_MOVE supported */
+#define KEYCTL_CAPS1_NS_KEYRING_NAME 0x01 /* Keyring names are per-user_namespace */
#endif /* _LINUX_KEYCTL_H */
diff --git a/kernel/user.c b/kernel/user.c
index 88b834f0eebc..50979fd1b7aa 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -62,6 +62,9 @@ struct user_namespace init_user_ns = {
.ns.ops = &userns_operations,
#endif
.flags = USERNS_INIT_FLAGS,
+#ifdef CONFIG_KEYS
+ .keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
+#endif
#ifdef CONFIG_PERSISTENT_KEYRINGS
.persistent_keyring_register_sem =
__RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 923414a246e9..bda6e890ad88 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -133,6 +133,9 @@ int create_user_ns(struct cred *new)
ns->flags = parent_ns->flags;
mutex_unlock(&userns_state_mutex);
+#ifdef CONFIG_KEYS
+ INIT_LIST_HEAD(&ns->keyring_name_list);
+#endif
#ifdef CONFIG_PERSISTENT_KEYRINGS
init_rwsem(&ns->persistent_keyring_register_sem);
#endif
@@ -196,9 +199,7 @@ static void free_user_ns(struct work_struct *work)
kfree(ns->projid_map.reverse);
}
retire_userns_sysctls(ns);
-#ifdef CONFIG_PERSISTENT_KEYRINGS
- key_put(ns->persistent_keyring_register);
-#endif
+ key_free_user_ns(ns);
ns_free_inum(&ns->ns);
kmem_cache_free(user_ns_cachep, ns);
dec_user_namespaces(ucounts);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 169409b611b0..37d2940cb10a 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -40,6 +40,7 @@ static const unsigned char keyrings_capabilities[1] = {
KEYCTL_CAPS0_RESTRICT_KEYRING |
KEYCTL_CAPS0_MOVE
),
+ [1] = (KEYCTL_CAPS1_NS_KEYRING_NAME),
};
static int key_get_type_from_user(char *type,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 20891cd198f0..fe851292509e 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -16,6 +16,7 @@
#include <linux/security.h>
#include <linux/seq_file.h>
#include <linux/err.h>
+#include <linux/user_namespace.h>
#include <keys/keyring-type.h>
#include <keys/user-type.h>
#include <linux/assoc_array_priv.h>
@@ -28,11 +29,6 @@
*/
#define KEYRING_SEARCH_MAX_DEPTH 6
-/*
- * We keep all named keyrings in a hash to speed looking them up.
- */
-#define KEYRING_NAME_HASH_SIZE (1 << 5)
-
/*
* We mark pointers we pass to the associative array with bit 1 set if
* they're keyrings and clear otherwise.
@@ -55,17 +51,20 @@ static inline void *keyring_key_to_ptr(struct key *key)
return key;
}
-static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE];
static DEFINE_RWLOCK(keyring_name_lock);
-static inline unsigned keyring_hash(const char *desc)
+/*
+ * Clean up the bits of user_namespace that belong to us.
+ */
+void key_free_user_ns(struct user_namespace *ns)
{
- unsigned bucket = 0;
-
- for (; *desc; desc++)
- bucket += (unsigned char)*desc;
+ write_lock(&keyring_name_lock);
+ list_del_init(&ns->keyring_name_list);
+ write_unlock(&keyring_name_lock);
- return bucket & (KEYRING_NAME_HASH_SIZE - 1);
+#ifdef CONFIG_PERSISTENT_KEYRINGS
+ key_put(ns->persistent_keyring_register);
+#endif
}
/*
@@ -104,23 +103,17 @@ static DEFINE_MUTEX(keyring_serialise_link_lock);
/*
* Publish the name of a keyring so that it can be found by name (if it has
- * one).
+ * one and it doesn't begin with a dot).
*/
static void keyring_publish_name(struct key *keyring)
{
- int bucket;
-
- if (keyring->description) {
- bucket = keyring_hash(keyring->description);
+ struct user_namespace *ns = current_user_ns();
+ if (keyring->description &&
+ keyring->description[0] &&
+ keyring->description[0] != '.') {
write_lock(&keyring_name_lock);
-
- if (!keyring_name_hash[bucket].next)
- INIT_LIST_HEAD(&keyring_name_hash[bucket]);
-
- list_add_tail(&keyring->name_link,
- &keyring_name_hash[bucket]);
-
+ list_add_tail(&keyring->name_link, &ns->keyring_name_list);
write_unlock(&keyring_name_lock);
}
}
@@ -1097,50 +1090,44 @@ key_ref_t find_key_to_update(key_ref_t keyring_ref,
*/
struct key *find_keyring_by_name(const char *name, bool uid_keyring)
{
+ struct user_namespace *ns = current_user_ns();
struct key *keyring;
- int bucket;
if (!name)
return ERR_PTR(-EINVAL);
- bucket = keyring_hash(name);
-
read_lock(&keyring_name_lock);
- if (keyring_name_hash[bucket].next) {
- /* search this hash bucket for a keyring with a matching name
- * that's readable and that hasn't been revoked */
- list_for_each_entry(keyring,
- &keyring_name_hash[bucket],
- name_link
- ) {
- if (!kuid_has_mapping(current_user_ns(), keyring->user->uid))
- continue;
-
- if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
- continue;
+ /* Search this hash bucket for a keyring with a matching name that
+ * grants Search permission and that hasn't been revoked
+ */
+ list_for_each_entry(keyring, &ns->keyring_name_list, name_link) {
+ if (!kuid_has_mapping(ns, keyring->user->uid))
+ continue;
- if (strcmp(keyring->description, name) != 0)
- continue;
+ if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
+ continue;
- if (uid_keyring) {
- if (!test_bit(KEY_FLAG_UID_KEYRING,
- &keyring->flags))
- continue;
- } else {
- if (key_permission(make_key_ref(keyring, 0),
- KEY_NEED_SEARCH) < 0)
- continue;
- }
+ if (strcmp(keyring->description, name) != 0)
+ continue;
- /* we've got a match but we might end up racing with
- * key_cleanup() if the keyring is currently 'dead'
- * (ie. it has a zero usage count) */
- if (!refcount_inc_not_zero(&keyring->usage))
+ if (uid_keyring) {
+ if (!test_bit(KEY_FLAG_UID_KEYRING,
+ &keyring->flags))
+ continue;
+ } else {
+ if (key_permission(make_key_ref(keyring, 0),
+ KEY_NEED_SEARCH) < 0)
continue;
- keyring->last_used_at = ktime_get_real_seconds();
- goto out;
}
+
+ /* we've got a match but we might end up racing with
+ * key_cleanup() if the keyring is currently 'dead'
+ * (ie. it has a zero usage count) */
+ if (!refcount_inc_not_zero(&keyring->usage))
+ continue;
+ keyring->last_used_at = ktime_get_real_seconds();
+ goto out;
}
keyring = ERR_PTR(-ENOKEY);
^ permalink raw reply related
* [PATCH 3/9] keys: Add a 'recurse' flag for keyring searches [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Add a 'recurse' flag for keyring searches so that the flag can be omitted
and recursion disabled, thereby allowing just the nominated keyring to be
searched and none of the children.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 10 ++++++----
certs/blacklist.c | 2 +-
crypto/asymmetric_keys/asymmetric_type.c | 2 +-
include/linux/key.h | 3 ++-
lib/digsig.c | 2 +-
net/rxrpc/security.c | 2 +-
security/integrity/digsig_asymmetric.c | 4 ++--
security/keys/internal.h | 1 +
security/keys/keyctl.c | 2 +-
security/keys/keyring.c | 12 ++++++++++--
security/keys/proc.c | 3 ++-
security/keys/process_keys.c | 3 ++-
security/keys/request_key.c | 3 ++-
security/keys/request_key_auth.c | 3 ++-
14 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index a0e245f9576f..ae930ae9d590 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1162,11 +1162,13 @@ payload contents" for more information.
key_ref_t keyring_search(key_ref_t keyring_ref,
const struct key_type *type,
- const char *description)
+ const char *description,
+ bool recurse)
- This searches the keyring tree specified for a matching key. Error ENOKEY
- is returned upon failure (use IS_ERR/PTR_ERR to determine). If successful,
- the returned key will need to be released.
+ This searches the specified keyring only (recurse == false) or keyring tree
+ (recurse == true) specified for a matching key. Error ENOKEY is returned
+ upon failure (use IS_ERR/PTR_ERR to determine). If successful, the returned
+ key will need to be released.
The possession attribute from the keyring reference is used to control
access through the permissions mask and is propagated to the returned key
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 3a507b9e2568..181cb7fa9540 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -128,7 +128,7 @@ int is_hash_blacklisted(const u8 *hash, size_t hash_len, const char *type)
*p = 0;
kref = keyring_search(make_key_ref(blacklist_keyring, true),
- &key_type_blacklist, buffer);
+ &key_type_blacklist, buffer, false);
if (!IS_ERR(kref)) {
key_ref_put(kref);
ret = -EKEYREJECTED;
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 69a0788a7de5..084027ef3121 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -87,7 +87,7 @@ struct key *find_asymmetric_key(struct key *keyring,
pr_debug("Look up: \"%s\"\n", req);
ref = keyring_search(make_key_ref(keyring, 1),
- &key_type_asymmetric, req);
+ &key_type_asymmetric, req, true);
if (IS_ERR(ref))
pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref));
kfree(req);
diff --git a/include/linux/key.h b/include/linux/key.h
index fb2debcacea0..ff102731b3db 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -341,7 +341,8 @@ extern int keyring_clear(struct key *keyring);
extern key_ref_t keyring_search(key_ref_t keyring,
struct key_type *type,
- const char *description);
+ const char *description,
+ bool recurse);
extern int keyring_add_key(struct key *keyring,
struct key *key);
diff --git a/lib/digsig.c b/lib/digsig.c
index 3b0a579bdcdf..3782af401c68 100644
--- a/lib/digsig.c
+++ b/lib/digsig.c
@@ -221,7 +221,7 @@ int digsig_verify(struct key *keyring, const char *sig, int siglen,
/* search in specific keyring */
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1UL),
- &key_type_user, name);
+ &key_type_user, name, true);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
diff --git a/net/rxrpc/security.c b/net/rxrpc/security.c
index c4479afe8ae7..2cfc7125bc41 100644
--- a/net/rxrpc/security.c
+++ b/net/rxrpc/security.c
@@ -148,7 +148,7 @@ int rxrpc_init_server_conn_security(struct rxrpc_connection *conn)
/* look through the service's keyring */
kref = keyring_search(make_key_ref(rx->securities, 1UL),
- &key_type_rxrpc_s, kdesc);
+ &key_type_rxrpc_s, kdesc, true);
if (IS_ERR(kref)) {
read_unlock(&local->services_lock);
_leave(" = %ld [search]", PTR_ERR(kref));
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 99080871eb9f..358f614811e8 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -39,7 +39,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
key_ref_t kref;
kref = keyring_search(make_key_ref(key, 1),
- &key_type_asymmetric, name);
+ &key_type_asymmetric, name, true);
if (!IS_ERR(kref)) {
pr_err("Key '%s' is in ima_blacklist_keyring\n", name);
return ERR_PTR(-EKEYREJECTED);
@@ -51,7 +51,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1),
- &key_type_asymmetric, name);
+ &key_type_asymmetric, name, true);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 4305414795ae..aa361299a3ec 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -127,6 +127,7 @@ struct keyring_search_context {
#define KEYRING_SEARCH_NO_CHECK_PERM 0x0008 /* Don't check permissions */
#define KEYRING_SEARCH_DETECT_TOO_DEEP 0x0010 /* Give an error on excessive depth */
#define KEYRING_SEARCH_SKIP_EXPIRED 0x0020 /* Ignore expired keys (intention to replace) */
+#define KEYRING_SEARCH_RECURSE 0x0040 /* Search child keyrings also */
int (*iterator)(const void *object, void *iterator_data);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 9f418e66f067..169409b611b0 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -762,7 +762,7 @@ long keyctl_keyring_search(key_serial_t ringid,
}
/* do the search */
- key_ref = keyring_search(keyring_ref, ktype, description);
+ key_ref = keyring_search(keyring_ref, ktype, description, true);
if (IS_ERR(key_ref)) {
ret = PTR_ERR(key_ref);
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index a5ee3b4d2eb8..20891cd198f0 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -685,6 +685,9 @@ static bool search_nested_keyrings(struct key *keyring,
* Non-keyrings avoid the leftmost branch of the root entirely (root
* slots 1-15).
*/
+ if (!(ctx->flags & KEYRING_SEARCH_RECURSE))
+ goto not_this_keyring;
+
ptr = READ_ONCE(keyring->keys.root);
if (!ptr)
goto not_this_keyring;
@@ -885,13 +888,15 @@ key_ref_t keyring_search_rcu(key_ref_t keyring_ref,
* @keyring: The root of the keyring tree to be searched.
* @type: The type of keyring we want to find.
* @description: The name of the keyring we want to find.
+ * @recurse: True to search the children of @keyring also
*
* As keyring_search_rcu() above, but using the current task's credentials and
* type's default matching function and preferred search method.
*/
key_ref_t keyring_search(key_ref_t keyring,
struct key_type *type,
- const char *description)
+ const char *description,
+ bool recurse)
{
struct keyring_search_context ctx = {
.index_key.type = type,
@@ -906,6 +911,8 @@ key_ref_t keyring_search(key_ref_t keyring,
key_ref_t key;
int ret;
+ if (recurse)
+ ctx.flags |= KEYRING_SEARCH_RECURSE;
if (type->match_preparse) {
ret = type->match_preparse(&ctx.match_data);
if (ret < 0)
@@ -1176,7 +1183,8 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
.flags = (KEYRING_SEARCH_NO_STATE_CHECK |
KEYRING_SEARCH_NO_UPDATE_TIME |
KEYRING_SEARCH_NO_CHECK_PERM |
- KEYRING_SEARCH_DETECT_TOO_DEEP),
+ KEYRING_SEARCH_DETECT_TOO_DEEP |
+ KEYRING_SEARCH_RECURSE),
};
rcu_read_lock();
diff --git a/security/keys/proc.c b/security/keys/proc.c
index f081dceae3b9..b4f5ba56b9cb 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -170,7 +170,8 @@ static int proc_keys_show(struct seq_file *m, void *v)
.match_data.cmp = lookup_user_key_possessed,
.match_data.raw_data = key,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_NO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_NO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
key_ref = make_key_ref(key, 0);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index f8ffb06d0297..b07f768d23dc 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -531,7 +531,8 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
struct keyring_search_context ctx = {
.match_data.cmp = lookup_user_key_possessed,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_NO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_NO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
struct request_key_auth *rka;
struct key *key;
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 36c55ef47b9e..1ffd3803ce29 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -569,7 +569,8 @@ struct key *request_key_and_link(struct key_type *type,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = (KEYRING_SEARCH_DO_STATE_CHECK |
- KEYRING_SEARCH_SKIP_EXPIRED),
+ KEYRING_SEARCH_SKIP_EXPIRED |
+ KEYRING_SEARCH_RECURSE),
};
struct key *key;
key_ref_t key_ref;
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 99ed7a8a273d..f613987e8a63 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -252,7 +252,8 @@ struct key *key_get_instantiation_authkey(key_serial_t target_id)
.match_data.cmp = key_default_cmp,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
- .flags = KEYRING_SEARCH_DO_STATE_CHECK,
+ .flags = (KEYRING_SEARCH_DO_STATE_CHECK |
+ KEYRING_SEARCH_RECURSE),
};
struct key *authkey;
key_ref_t authkey_ref;
^ permalink raw reply related
* [PATCH 2/9] keys: Cache the hash value to avoid lots of recalculation [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Cache the hash of the key's type and description in the index key so that
we're not recalculating it every time we look at a key during a search.
The hash function does a bunch of multiplications, so evading those is
probably worthwhile - especially as this is done for every key examined
during a search.
This also allows the methods used by assoc_array to get chunks of index-key
to be simplified.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/linux/key.h | 3 +++
security/keys/internal.h | 8 +-------
security/keys/key.c | 2 +-
security/keys/keyring.c | 28 ++++++++++++++++++++--------
4 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index 86ccc2d010f6..fb2debcacea0 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -86,6 +86,8 @@ struct keyring_list;
struct keyring_name;
struct keyring_index_key {
+ /* [!] If this structure is altered, the union in struct key must change too! */
+ unsigned long hash; /* Hash value */
union {
struct {
#ifdef __LITTLE_ENDIAN /* Put desc_len at the LSB of x */
@@ -213,6 +215,7 @@ struct key {
union {
struct keyring_index_key index_key;
struct {
+ unsigned long hash;
unsigned long len_desc;
struct key_type *type; /* type of key */
char *description;
diff --git a/security/keys/internal.h b/security/keys/internal.h
index ee71c72fc5f0..4305414795ae 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -89,13 +89,7 @@ extern spinlock_t key_serial_lock;
extern struct mutex key_construction_mutex;
extern wait_queue_head_t request_key_conswq;
-
-static inline void key_set_index_key(struct keyring_index_key *index_key)
-{
- size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
- memcpy(index_key->desc, index_key->description, n);
-}
-
+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);
diff --git a/security/keys/key.c b/security/keys/key.c
index 0a3828f15f57..9d52f2472a09 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -285,12 +285,12 @@ struct key *key_alloc(struct key_type *type, const char *desc,
key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL);
if (!key->index_key.description)
goto no_memory_3;
+ key->index_key.type = type;
key_set_index_key(&key->index_key);
refcount_set(&key->usage, 1);
init_rwsem(&key->sem);
lockdep_set_class(&key->sem, &type->lock_class);
- key->index_key.type = type;
key->user = user;
key->quotalen = quotalen;
key->datalen = type->def_datalen;
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index ebf52077598f..a5ee3b4d2eb8 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -168,7 +168,7 @@ static u64 mult_64x32_and_fold(u64 x, u32 y)
/*
* Hash a key type and description.
*/
-static unsigned long hash_key_type_and_desc(const struct keyring_index_key *index_key)
+static void hash_key_type_and_desc(struct keyring_index_key *index_key)
{
const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP;
const unsigned long fan_mask = ASSOC_ARRAY_FAN_MASK;
@@ -206,10 +206,22 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
* zero for keyrings and non-zero otherwise.
*/
if (index_key->type != &key_type_keyring && (hash & fan_mask) == 0)
- return hash | (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1;
- if (index_key->type == &key_type_keyring && (hash & fan_mask) != 0)
- return (hash + (hash << level_shift)) & ~fan_mask;
- return hash;
+ hash |= (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1;
+ else if (index_key->type == &key_type_keyring && (hash & fan_mask) != 0)
+ hash = (hash + (hash << level_shift)) & ~fan_mask;
+ index_key->hash = hash;
+}
+
+/*
+ * Finalise an index key to include a part of the description actually in the
+ * index key and to add in the hash too.
+ */
+void key_set_index_key(struct keyring_index_key *index_key)
+{
+ size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
+ memcpy(index_key->desc, index_key->description, n);
+
+ hash_key_type_and_desc(index_key);
}
/*
@@ -227,7 +239,7 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
level /= ASSOC_ARRAY_KEY_CHUNK_SIZE;
switch (level) {
case 0:
- return hash_key_type_and_desc(index_key);
+ return index_key->hash;
case 1:
return index_key->x;
case 2:
@@ -280,8 +292,8 @@ static int keyring_diff_objects(const void *object, const void *data)
int level, i;
level = 0;
- seg_a = hash_key_type_and_desc(a);
- seg_b = hash_key_type_and_desc(b);
+ seg_a = a->hash;
+ seg_b = b->hash;
if ((seg_a ^ seg_b) != 0)
goto differ;
level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
^ permalink raw reply related
* [PATCH 1/9] keys: Simplify key description management [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
In-Reply-To: <156096036064.6697.2432500504898119675.stgit@warthog.procyon.org.uk>
Simplify key description management by cramming the word containing the
length with the first few chars of the description also. This simplifies
the code that generates the index-key used by assoc_array. It should speed
up key searching a bit too.
Signed-off-by: David Howells <dhowells@redhat.com>
---
include/linux/key.h | 14 ++++++++-
security/keys/internal.h | 6 ++++
security/keys/key.c | 2 +
security/keys/keyring.c | 70 +++++++++++++-------------------------------
security/keys/persistent.c | 1 +
5 files changed, 43 insertions(+), 50 deletions(-)
diff --git a/include/linux/key.h b/include/linux/key.h
index 4cd5669184f3..86ccc2d010f6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -86,9 +86,20 @@ struct keyring_list;
struct keyring_name;
struct keyring_index_key {
+ union {
+ struct {
+#ifdef __LITTLE_ENDIAN /* Put desc_len at the LSB of x */
+ u8 desc_len;
+ char desc[sizeof(long) - 1]; /* First few chars of description */
+#else
+ char desc[sizeof(long) - 1]; /* First few chars of description */
+ u8 desc_len;
+#endif
+ };
+ unsigned long x;
+ };
struct key_type *type;
const char *description;
- size_t desc_len;
};
union key_payload {
@@ -202,6 +213,7 @@ struct key {
union {
struct keyring_index_key index_key;
struct {
+ unsigned long len_desc;
struct key_type *type; /* type of key */
char *description;
};
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 3d5c08db74d2..ee71c72fc5f0 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -90,6 +90,12 @@ extern struct mutex key_construction_mutex;
extern wait_queue_head_t request_key_conswq;
+static inline void key_set_index_key(struct keyring_index_key *index_key)
+{
+ size_t n = min_t(size_t, index_key->desc_len, sizeof(index_key->desc));
+ memcpy(index_key->desc, index_key->description, n);
+}
+
extern struct key_type *key_type_lookup(const char *type);
extern void key_type_put(struct key_type *ktype);
diff --git a/security/keys/key.c b/security/keys/key.c
index e792d65c0af8..0a3828f15f57 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -285,6 +285,7 @@ struct key *key_alloc(struct key_type *type, const char *desc,
key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL);
if (!key->index_key.description)
goto no_memory_3;
+ key_set_index_key(&key->index_key);
refcount_set(&key->usage, 1);
init_rwsem(&key->sem);
@@ -868,6 +869,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
goto error_free_prep;
}
index_key.desc_len = strlen(index_key.description);
+ key_set_index_key(&index_key);
ret = __key_link_lock(keyring, &index_key);
if (ret < 0) {
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index afa6d4024c67..ebf52077598f 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -179,9 +179,9 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
int n, desc_len = index_key->desc_len;
type = (unsigned long)index_key->type;
-
acc = mult_64x32_and_fold(type, desc_len + 13);
acc = mult_64x32_and_fold(acc, 9207);
+
for (;;) {
n = desc_len;
if (n <= 0)
@@ -215,23 +215,13 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde
/*
* Build the next index key chunk.
*
- * On 32-bit systems the index key is laid out as:
- *
- * 0 4 5 9...
- * hash desclen typeptr desc[]
- *
- * On 64-bit systems:
- *
- * 0 8 9 17...
- * hash desclen typeptr desc[]
- *
* We return it one word-sized chunk at a time.
*/
static unsigned long keyring_get_key_chunk(const void *data, int level)
{
const struct keyring_index_key *index_key = data;
unsigned long chunk = 0;
- long offset = 0;
+ const u8 *d;
int desc_len = index_key->desc_len, n = sizeof(chunk);
level /= ASSOC_ARRAY_KEY_CHUNK_SIZE;
@@ -239,33 +229,23 @@ static unsigned long keyring_get_key_chunk(const void *data, int level)
case 0:
return hash_key_type_and_desc(index_key);
case 1:
- return ((unsigned long)index_key->type << 8) | desc_len;
+ return index_key->x;
case 2:
- if (desc_len == 0)
- return (u8)((unsigned long)index_key->type >>
- (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8));
- n--;
- offset = 1;
- /* fall through */
+ return (unsigned long)index_key->type;
default:
- offset += sizeof(chunk) - 1;
- offset += (level - 3) * sizeof(chunk);
- if (offset >= desc_len)
+ level -= 3;
+ if (desc_len <= sizeof(index_key->desc))
return 0;
- desc_len -= offset;
+
+ d = index_key->description + sizeof(index_key->desc);
+ d += level * sizeof(long);
+ desc_len -= sizeof(index_key->desc);
if (desc_len > n)
desc_len = n;
- offset += desc_len;
do {
chunk <<= 8;
- chunk |= ((u8*)index_key->description)[--offset];
+ chunk |= *d++;
} while (--desc_len > 0);
-
- if (level == 2) {
- chunk <<= 8;
- chunk |= (u8)((unsigned long)index_key->type >>
- (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8));
- }
return chunk;
}
}
@@ -304,39 +284,28 @@ static int keyring_diff_objects(const void *object, const void *data)
seg_b = hash_key_type_and_desc(b);
if ((seg_a ^ seg_b) != 0)
goto differ;
+ level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
/* The number of bits contributed by the hash is controlled by a
* constant in the assoc_array headers. Everything else thereafter we
* can deal with as being machine word-size dependent.
*/
- level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8;
- seg_a = a->desc_len;
- seg_b = b->desc_len;
+ seg_a = a->x;
+ seg_b = b->x;
if ((seg_a ^ seg_b) != 0)
goto differ;
+ level += sizeof(unsigned long);
/* The next bit may not work on big endian */
- level++;
seg_a = (unsigned long)a->type;
seg_b = (unsigned long)b->type;
if ((seg_a ^ seg_b) != 0)
goto differ;
-
level += sizeof(unsigned long);
- if (a->desc_len == 0)
- goto same;
- i = 0;
- if (((unsigned long)a->description | (unsigned long)b->description) &
- (sizeof(unsigned long) - 1)) {
- do {
- seg_a = *(unsigned long *)(a->description + i);
- seg_b = *(unsigned long *)(b->description + i);
- if ((seg_a ^ seg_b) != 0)
- goto differ_plus_i;
- i += sizeof(unsigned long);
- } while (i < (a->desc_len & (sizeof(unsigned long) - 1)));
- }
+ i = sizeof(a->desc);
+ if (a->desc_len <= i)
+ goto same;
for (; i < a->desc_len; i++) {
seg_a = *(unsigned char *)(a->description + i);
@@ -662,6 +631,9 @@ static bool search_nested_keyrings(struct key *keyring,
BUG_ON((ctx->flags & STATE_CHECKS) == 0 ||
(ctx->flags & STATE_CHECKS) == STATE_CHECKS);
+ if (ctx->index_key.description)
+ key_set_index_key(&ctx->index_key);
+
/* Check to see if this top-level keyring is what we are looking for
* and whether it is valid or not.
*/
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index d0cb5b32eff7..fc29ec59efa7 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -87,6 +87,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
index_key.type = &key_type_keyring;
index_key.description = buf;
index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
+ key_set_index_key(&index_key);
if (ns->persistent_keyring_register) {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
^ permalink raw reply related
* [PATCH 0/9] keys: Namespacing [ver #4]
From: David Howells @ 2019-06-19 16:06 UTC (permalink / raw)
To: ebiederm, keyrings
Cc: linux-cifs, linux-nfs, netdev, linux-afs, dhowells, dwalsh,
vgoyal, linux-security-module, linux-fsdevel, linux-kernel
Here are some patches to make keys and keyrings more namespace aware.
Firstly some miscellaneous patches to make the process easier:
(1) Simplify key index_key handling so that the word-sized chunks
assoc_array requires don't have to be shifted about, making it easier
to add more bits into the key.
(2) Cache the hash value in the key so that we don't have to calculate on
every key we examine during a search (it involves a bunch of
multiplications).
(3) Allow keying_search() to search non-recursively.
Then the main patches:
(4) Make it so that keyring names are per-user_namespace from the point of
view of KEYCTL_JOIN_SESSION_KEYRING so that they're not accessible
cross-user_namespace.
keyctl_capabilities() shows KEYCTL_CAPS1_NS_KEYRING_NAME for this.
(5) Move the user and user-session keyrings to the user_namespace rather
than the user_struct. This prevents them propagating directly across
user_namespaces boundaries (ie. the KEY_SPEC_* flags will only pick
from the current user_namespace).
(6) Make it possible to include the target namespace in which the key shall
operate in the index_key. This will allow the possibility of multiple
keys with the same description, but different target domains to be held
in the same keyring.
keyctl_capabilities() shows KEYCTL_CAPS1_NS_KEY_TAG for this.
(7) Make it so that keys are implicitly invalidated by removal of a domain
tag, causing them to be garbage collected.
(8) Institute a network namespace domain tag that allows keys to be
differentiated by the network namespace in which they operate. New keys
that are of a type marked 'KEY_TYPE_NET_DOMAIN' are assigned the network
domain in force when they are created.
(9) Make it so that the desired network namespace can be handed down into the
request_key() mechanism. This allows AFS, NFS, etc. to request keys
specific to the network namespace of the superblock.
This also means that the keys in the DNS record cache are thenceforth
namespaced, provided network filesystems pass the appropriate network
namespace down into dns_query().
For DNS, AFS and NFS are good; CIFS and Ceph are not. Other cache
keyrings, such as idmapper keyrings, also need to set the domain tag -
for which they need access to the network namespace of the superblock.
The patches can be found on the following branch:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-namespace
David
---
David Howells (9):
keys: Simplify key description management
keys: Cache the hash value to avoid lots of recalculation
keys: Add a 'recurse' flag for keyring searches
keys: Namespace keyring names
keys: Move the user and user-session keyrings to the user_namespace
keys: Include target namespace in match criteria
keys: Garbage collect keys for which the domain has been removed
keys: Network namespace domain tag
keys: Pass the network namespace into request_key mechanism
Documentation/security/keys/core.rst | 38 +++-
Documentation/security/keys/request-key.rst | 29 ++-
certs/blacklist.c | 2
crypto/asymmetric_keys/asymmetric_type.c | 2
fs/afs/addr_list.c | 4
fs/afs/dynroot.c | 8 +
fs/cifs/dns_resolve.c | 3
fs/nfs/dns_resolve.c | 3
fs/nfs/nfs4idmap.c | 2
include/linux/dns_resolver.h | 3
include/linux/key-type.h | 3
include/linux/key.h | 81 ++++++++
include/linux/sched/user.h | 14 -
include/linux/user_namespace.h | 12 +
include/net/net_namespace.h | 3
include/uapi/linux/keyctl.h | 2
kernel/user.c | 8 -
kernel/user_namespace.c | 9 -
lib/digsig.c | 2
net/ceph/messenger.c | 3
net/core/net_namespace.c | 19 ++
net/dns_resolver/dns_key.c | 1
net/dns_resolver/dns_query.c | 7 +
net/rxrpc/key.c | 6 -
net/rxrpc/security.c | 2
security/integrity/digsig_asymmetric.c | 4
security/keys/gc.c | 2
security/keys/internal.h | 10 +
security/keys/key.c | 5 -
security/keys/keyctl.c | 6 -
security/keys/keyring.c | 263 +++++++++++++++------------
security/keys/persistent.c | 10 +
security/keys/proc.c | 3
security/keys/process_keys.c | 262 +++++++++++++++++----------
security/keys/request_key.c | 62 ++++--
security/keys/request_key_auth.c | 3
36 files changed, 587 insertions(+), 309 deletions(-)
^ permalink raw reply
* Re: [PATCH v2 00/25] LSM: Module stacking for AppArmor
From: Casey Schaufler @ 2019-06-19 15:39 UTC (permalink / raw)
To: Kees Cook
Cc: casey.schaufler, jmorris, linux-security-module, selinux,
john.johansen, penguin-kernel, paul, sds, casey
In-Reply-To: <201906182133.EBF2C78D@keescook>
On 6/18/2019 9:34 PM, Kees Cook wrote:
> On Tue, Jun 18, 2019 at 04:05:26PM -0700, Casey Schaufler wrote:
>> Patches 0001-0003 complete the process of moving managment
>> of security blobs that might be shared from the individual
>> modules to the infrastructure.
> I think these are happy stand-alone patches and should just go into the
> common LSM tree for v5.3.
They don't have a user without the rest of the patchset,
but they do make the internals more consistent. James' call.
^ permalink raw reply
* [PATCH 6/6] keys: Kill off request_key_async{, _with_auxdata} [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
Kill off request_key_async{,_with_auxdata}() as they're not currently used.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 32 -----------------
Documentation/security/keys/request-key.rst | 23 +-----------
include/linux/key.h | 11 ------
security/keys/request_key.c | 50 ---------------------------
4 files changed, 2 insertions(+), 114 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 003f1452a5b7..a0e245f9576f 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1115,38 +1115,6 @@ payload contents" for more information.
is a blob of length callout_len, if given (the length may be 0).
- * A key can be requested asynchronously by calling one of::
-
- struct key *request_key_async(const struct key_type *type,
- const char *description,
- const void *callout_info,
- size_t callout_len);
-
- or::
-
- struct key *request_key_async_with_auxdata(const struct key_type *type,
- const char *description,
- const char *callout_info,
- size_t callout_len,
- void *aux);
-
- which are asynchronous equivalents of request_key() and
- request_key_with_auxdata() respectively.
-
- These two functions return with the key potentially still under
- construction. To wait for construction completion, the following should be
- called::
-
- int wait_for_key_construction(struct key *key, bool intr);
-
- The function will wait for the key to finish being constructed and then
- invokes key_validate() to return an appropriate value to indicate the state
- of the key (0 indicates the key is usable).
-
- If intr is true, then the wait can be interrupted by a signal, in which
- case error ERESTARTSYS will be returned.
-
-
* To search for a key under RCU conditions, call::
struct key *request_key_rcu(const struct key_type *type,
diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst
index 45049abdf290..5a210baa583a 100644
--- a/Documentation/security/keys/request-key.rst
+++ b/Documentation/security/keys/request-key.rst
@@ -21,21 +21,6 @@ or::
size_t callout_len,
void *aux);
-or::
-
- struct key *request_key_async(const struct key_type *type,
- const char *description,
- const char *callout_info,
- size_t callout_len);
-
-or::
-
- struct key *request_key_async_with_auxdata(const struct key_type *type,
- const char *description,
- const char *callout_info,
- size_t callout_len,
- void *aux);
-
or::
struct key *request_key_rcu(const struct key_type *type,
@@ -53,15 +38,11 @@ does not need to link the key to a keyring to prevent it from being immediately
destroyed. The kernel interface returns a pointer directly to the key, and
it's up to the caller to destroy the key.
-The request_key*_with_auxdata() calls are like the in-kernel request_key*()
-calls, except that they permit auxiliary data to be passed to the upcaller (the
+The request_key_with_auxdata() calls is like the in-kernel request_key() call,
+except that they permit auxiliary data to be passed to the upcaller (the
default is NULL). This is only useful for those key types that define their
own upcall mechanism rather than using /sbin/request-key.
-The two async in-kernel calls may return keys that are still in the process of
-being constructed. The two non-async ones will wait for construction to
-complete first.
-
The request_key_rcu() call is like the in-kernel request_key() call, except
that it doesn't check for keys that are under construction and doesn't attempt
to construct missing keys.
diff --git a/include/linux/key.h b/include/linux/key.h
index 3604a554df99..4cd5669184f3 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -283,17 +283,6 @@ extern struct key *request_key_with_auxdata(struct key_type *type,
size_t callout_len,
void *aux);
-extern struct key *request_key_async(struct key_type *type,
- const char *description,
- const void *callout_info,
- size_t callout_len);
-
-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);
-
extern int wait_for_key_construction(struct key *key, bool intr);
extern int key_validate(const struct key *key);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index f289982cb5db..36c55ef47b9e 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -739,56 +739,6 @@ struct key *request_key_with_auxdata(struct key_type *type,
}
EXPORT_SYMBOL(request_key_with_auxdata);
-/*
- * request_key_async - Request a key (allow async construction)
- * @type: Type of key.
- * @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.
- *
- * 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
- * no auxiliary data can be passed.
- *
- * The caller should call wait_for_key_construction() to wait for the
- * completion of the returned key if it is still undergoing construction.
- */
-struct key *request_key_async(struct key_type *type,
- const char *description,
- const void *callout_info,
- size_t callout_len)
-{
- return request_key_and_link(type, description, callout_info,
- callout_len, NULL, NULL,
- KEY_ALLOC_IN_QUOTA);
-}
-EXPORT_SYMBOL(request_key_async);
-
-/*
- * request a key with auxiliary data for the upcaller (allow async construction)
- * @type: Type of key.
- * @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.
- * @aux: Auxiliary data for the upcall.
- *
- * 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.
- *
- * The caller should call wait_for_key_construction() to wait for the
- * completion of the returned key if it is still undergoing construction.
- */
-struct key *request_key_async_with_auxdata(struct key_type *type,
- const char *description,
- const void *callout_info,
- size_t callout_len,
- void *aux)
-{
- return request_key_and_link(type, description, callout_info,
- callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
-}
-EXPORT_SYMBOL(request_key_async_with_auxdata);
-
/**
* request_key_rcu - Request key from RCU-read-locked context
* @type: The type of key we want.
^ permalink raw reply related
* [PATCH 5/6] keys: Cache result of request_key*() temporarily in task_struct [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
If a filesystem uses keys to hold authentication tokens, then it needs a
token for each VFS operation that might perform an authentication check -
either by passing it to the server, or using to perform a check based on
authentication data cached locally.
For open files this isn't a problem, since the key should be cached in the
file struct since it represents the subject performing operations on that
file descriptor.
During pathwalk, however, there isn't anywhere to cache the key, except
perhaps in the nameidata struct - but that isn't exposed to the
filesystems. Further, a pathwalk can incur a lot of operations, calling
one or more of the following, for instance:
->lookup()
->permission()
->d_revalidate()
->d_automount()
->get_acl()
->getxattr()
on each dentry/inode it encounters - and each one may need to call
request_key(). And then, at the end of pathwalk, it will call the actual
operation:
->mkdir()
->mknod()
->getattr()
->open()
...
which may need to go and get the token again.
However, it is very likely that all of the operations on a single
dentry/inode - and quite possibly a sequence of them - will all want to use
the same authentication token, which suggests that caching it would be a
good idea.
To this end:
(1) Make it so that a positive result of request_key() and co. that didn't
require upcalling to userspace is cached temporarily in task_struct.
(2) The cache is 1 deep, so a new result displaces the old one.
(3) The key is released by exit and by notify-resume.
(4) The cache is cleared in a newly forked process.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/request-key.rst | 7 ++++-
include/linux/sched.h | 5 ++++
include/linux/tracehook.h | 7 +++++
kernel/cred.c | 9 +++++++
security/keys/Kconfig | 17 ++++++++++++
security/keys/request_key.c | 37 +++++++++++++++++++++++++++
6 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst
index 7caedc4d29f1..45049abdf290 100644
--- a/Documentation/security/keys/request-key.rst
+++ b/Documentation/security/keys/request-key.rst
@@ -176,6 +176,9 @@ The process stops immediately a valid key is found with permission granted to
use it. Any error from a previous match attempt is discarded and the key is
returned.
+When request_key() is invoked, if CONFIG_KEYS_REQUEST_CACHE=y, a per-task
+one-key cache is first checked for a match.
+
When search_process_keyrings() is invoked, it performs the following searches
until one succeeds:
@@ -195,7 +198,9 @@ until one succeeds:
c) The calling process's session keyring is searched.
The moment one succeeds, all pending errors are discarded and the found key is
-returned.
+returned. If CONFIG_KEYS_REQUEST_CACHE=y, then that key is placed in the
+per-task cache, displacing the previous key. The cache is cleared on exit or
+just prior to resumption of userspace.
Only if all these fail does the whole thing fail with the highest priority
error. Note that several errors may have come from LSM.
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 11837410690f..e5f18857dd53 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -831,6 +831,11 @@ struct task_struct {
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
+#ifdef CONFIG_KEYS
+ /* Cached requested key. */
+ struct key *cached_requested_key;
+#endif
+
/*
* executable name, excluding path.
*
diff --git a/include/linux/tracehook.h b/include/linux/tracehook.h
index df20f8bdbfa3..81824467e6a6 100644
--- a/include/linux/tracehook.h
+++ b/include/linux/tracehook.h
@@ -187,6 +187,13 @@ static inline void tracehook_notify_resume(struct pt_regs *regs)
if (unlikely(current->task_works))
task_work_run();
+#ifdef CONFIG_KEYS_REQUEST_CACHE
+ if (unlikely(current->cached_requested_key)) {
+ key_put(current->cached_requested_key);
+ current->cached_requested_key = NULL;
+ }
+#endif
+
mem_cgroup_handle_over_high();
blkcg_maybe_throttle_current();
}
diff --git a/kernel/cred.c b/kernel/cred.c
index 3bd40de9e192..26da7e77098f 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -174,6 +174,11 @@ void exit_creds(struct task_struct *tsk)
validate_creds(cred);
alter_cred_subscribers(cred, -1);
put_cred(cred);
+
+#ifdef CONFIG_KEYS_REQUEST_CACHE
+ key_put(current->cached_requested_key);
+ current->cached_requested_key = NULL;
+#endif
}
/**
@@ -327,6 +332,10 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)
struct cred *new;
int ret;
+#ifdef CONFIG_KEYS_REQUEST_CACHE
+ p->cached_requested_key = NULL;
+#endif
+
if (
#ifdef CONFIG_KEYS
!p->cred->thread_keyring &&
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6462e6654ccf..a2c2259aef5f 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -24,6 +24,23 @@ config KEYS_COMPAT
def_bool y
depends on COMPAT && KEYS
+config KEYS_REQUEST_CACHE
+ bool "Enable temporary caching of the last request_key() result"
+ help
+ This option causes the result of the last successful request_key()
+ call that didn't upcall to the kernel to be cached temporarily in the
+ task_struct. The cache is cleared by exit and just prior to the
+ resumption of userspace.
+
+ This allows the key used for multiple step processes where each step
+ wants to request a key that is likely the same as the one requested
+ by the last step to save on the searching.
+
+ An example of such a process is a pathwalk through a network
+ filesystem in which each method needs to request an authentication
+ key. Pathwalk will call multiple methods for each dentry traversed
+ (permission, d_revalidate, lookup, getxattr, getacl, ...).
+
config PERSISTENT_KEYRINGS
bool "Enable register of persistent per-UID keyrings"
depends on KEYS
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index b4b3677657d6..f289982cb5db 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -22,6 +22,31 @@
#define key_negative_timeout 60 /* default timeout on a negative key's existence */
+static struct key *check_cached_key(struct keyring_search_context *ctx)
+{
+#ifdef CONFIG_KEYS_REQUEST_CACHE
+ struct key *key = current->cached_requested_key;
+
+ if (key &&
+ ctx->match_data.cmp(key, &ctx->match_data) &&
+ !(key->flags & ((1 << KEY_FLAG_INVALIDATED) |
+ (1 << KEY_FLAG_REVOKED))))
+ return key_get(key);
+#endif
+ return NULL;
+}
+
+static void cache_requested_key(struct key *key)
+{
+#ifdef CONFIG_KEYS_REQUEST_CACHE
+ struct task_struct *t = current;
+
+ key_put(t->cached_requested_key);
+ t->cached_requested_key = key_get(key);
+ set_tsk_thread_flag(t, TIF_NOTIFY_RESUME);
+#endif
+}
+
/**
* complete_request_key - Complete the construction of a key.
* @authkey: The authorisation key.
@@ -562,6 +587,10 @@ struct key *request_key_and_link(struct key_type *type,
}
}
+ key = check_cached_key(&ctx);
+ if (key)
+ return key;
+
/* search all the process keyrings for a key */
rcu_read_lock();
key_ref = search_process_keyrings_rcu(&ctx);
@@ -587,6 +616,9 @@ struct key *request_key_and_link(struct key_type *type,
goto error_free;
}
}
+
+ /* Only cache the key on immediate success */
+ cache_requested_key(key);
} else if (PTR_ERR(key_ref) != -EAGAIN) {
key = ERR_CAST(key_ref);
} else {
@@ -786,6 +818,10 @@ struct key *request_key_rcu(struct key_type *type, const char *description)
kenter("%s,%s", type->name, description);
+ key = check_cached_key(&ctx);
+ if (key)
+ return key;
+
/* search all the process keyrings for a key */
key_ref = search_process_keyrings_rcu(&ctx);
if (IS_ERR(key_ref)) {
@@ -794,6 +830,7 @@ struct key *request_key_rcu(struct key_type *type, const char *description)
key = ERR_PTR(-ENOKEY);
} else {
key = key_ref_to_ptr(key_ref);
+ cache_requested_key(key);
}
kleave(" = %p", key);
^ permalink raw reply related
* [PATCH 4/6] keys: Provide request_key_rcu() [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
Provide a request_key_rcu() function that can be used to request a key
under RCU conditions. It can only search and check permissions; it cannot
allocate a new key, upcall or wait for an upcall to complete. It may
return a partially constructed key.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 10 ++++++
Documentation/security/keys/request-key.rst | 9 ++++++
include/linux/key.h | 3 ++
security/keys/request_key.c | 44 +++++++++++++++++++++++++++
4 files changed, 66 insertions(+)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 82dd457ff78d..003f1452a5b7 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1147,6 +1147,16 @@ payload contents" for more information.
case error ERESTARTSYS will be returned.
+ * To search for a key under RCU conditions, call::
+
+ struct key *request_key_rcu(const struct key_type *type,
+ const char *description);
+
+ which is similar to request_key() except that it does not check for keys
+ that are under construction and it will not call out to userspace to
+ construct a key if it can't find a match.
+
+
* When it is no longer required, the key should be released using::
void key_put(struct key *key);
diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst
index 07af991463b5..7caedc4d29f1 100644
--- a/Documentation/security/keys/request-key.rst
+++ b/Documentation/security/keys/request-key.rst
@@ -36,6 +36,11 @@ or::
size_t callout_len,
void *aux);
+or::
+
+ struct key *request_key_rcu(const struct key_type *type,
+ const char *description);
+
Or by userspace invoking the request_key system call::
key_serial_t request_key(const char *type,
@@ -57,6 +62,10 @@ The two async in-kernel calls may return keys that are still in the process of
being constructed. The two non-async ones will wait for construction to
complete first.
+The request_key_rcu() call is like the in-kernel request_key() call, except
+that it doesn't check for keys that are under construction and doesn't attempt
+to construct missing keys.
+
The userspace interface links the key to a keyring associated with the process
to prevent the key from going away, and returns the serial number of the key to
the caller.
diff --git a/include/linux/key.h b/include/linux/key.h
index 612e1cf84049..3604a554df99 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -274,6 +274,9 @@ extern struct key *request_key(struct key_type *type,
const char *description,
const char *callout_info);
+extern struct key *request_key_rcu(struct key_type *type,
+ const char *description);
+
extern struct key *request_key_with_auxdata(struct key_type *type,
const char *description,
const void *callout_info,
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index bf1d223ec21c..b4b3677657d6 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -756,3 +756,47 @@ struct key *request_key_async_with_auxdata(struct key_type *type,
callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
}
EXPORT_SYMBOL(request_key_async_with_auxdata);
+
+/**
+ * request_key_rcu - Request key from RCU-read-locked context
+ * @type: The type of key we want.
+ * @description: The name of the key we want.
+ *
+ * Request a key from a context that we may not sleep in (such as RCU-mode
+ * pathwalk). Keys under construction are ignored.
+ *
+ * Return a pointer to the found key if successful, -ENOKEY if we couldn't find
+ * a key or some other error if the key found was unsuitable or inaccessible.
+ */
+struct key *request_key_rcu(struct key_type *type, const char *description)
+{
+ struct keyring_search_context ctx = {
+ .index_key.type = type,
+ .index_key.description = description,
+ .index_key.desc_len = strlen(description),
+ .cred = current_cred(),
+ .match_data.cmp = key_default_cmp,
+ .match_data.raw_data = description,
+ .match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
+ .flags = (KEYRING_SEARCH_DO_STATE_CHECK |
+ KEYRING_SEARCH_SKIP_EXPIRED),
+ };
+ struct key *key;
+ key_ref_t key_ref;
+
+ kenter("%s,%s", type->name, description);
+
+ /* search all the process keyrings for a key */
+ key_ref = search_process_keyrings_rcu(&ctx);
+ if (IS_ERR(key_ref)) {
+ key = ERR_CAST(key_ref);
+ if (PTR_ERR(key_ref) == -EAGAIN)
+ key = ERR_PTR(-ENOKEY);
+ } else {
+ key = key_ref_to_ptr(key_ref);
+ }
+
+ kleave(" = %p", key);
+ return key;
+}
+EXPORT_SYMBOL(request_key_rcu);
^ permalink raw reply related
* [PATCH 3/6] keys: Move the RCU locks outwards from the keyring search functions [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
Move the RCU locks outwards from the keyring search functions so that it
will become possible to provide an RCU-capable partial request_key()
function in a later commit.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/request-key.rst | 2 -
include/keys/request_key_auth-type.h | 1
security/keys/internal.h | 6 +--
security/keys/keyring.c | 16 ++++---
security/keys/proc.c | 4 +-
security/keys/process_keys.c | 41 ++++++++----------
security/keys/request_key.c | 8 +++-
security/keys/request_key_auth.c | 60 ++++++++++++++++-----------
8 files changed, 77 insertions(+), 61 deletions(-)
diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst
index 600ad67d1707..07af991463b5 100644
--- a/Documentation/security/keys/request-key.rst
+++ b/Documentation/security/keys/request-key.rst
@@ -148,7 +148,7 @@ The Search Algorithm
A search of any particular keyring proceeds in the following fashion:
- 1) When the key management code searches for a key (keyring_search_aux) it
+ 1) When the key management code searches for a key (keyring_search_rcu) it
firstly calls key_permission(SEARCH) on the keyring it's starting with,
if this denies permission, it doesn't search further.
diff --git a/include/keys/request_key_auth-type.h b/include/keys/request_key_auth-type.h
index a726dd3f1dc6..2a046062bb42 100644
--- a/include/keys/request_key_auth-type.h
+++ b/include/keys/request_key_auth-type.h
@@ -18,6 +18,7 @@
* Authorisation record for request_key().
*/
struct request_key_auth {
+ struct rcu_head rcu;
struct key *target_key;
struct key *dest_keyring;
const struct cred *cred;
diff --git a/security/keys/internal.h b/security/keys/internal.h
index d04bff631227..3d5c08db74d2 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -139,11 +139,11 @@ struct keyring_search_context {
extern bool key_default_cmp(const struct key *key,
const struct key_match_data *match_data);
-extern key_ref_t keyring_search_aux(key_ref_t keyring_ref,
+extern key_ref_t keyring_search_rcu(key_ref_t keyring_ref,
struct keyring_search_context *ctx);
-extern key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx);
-extern key_ref_t search_process_keyrings(struct keyring_search_context *ctx);
+extern key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx);
+extern key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx);
extern struct key *find_keyring_by_name(const char *name, bool uid_keyring);
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 67066bb58b83..afa6d4024c67 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -835,7 +835,7 @@ static bool search_nested_keyrings(struct key *keyring,
}
/**
- * keyring_search_aux - Search a keyring tree for a key matching some criteria
+ * keyring_search_rcu - Search a keyring tree for a matching key under RCU
* @keyring_ref: A pointer to the keyring with possession indicator.
* @ctx: The keyring search context.
*
@@ -847,7 +847,9 @@ static bool search_nested_keyrings(struct key *keyring,
* addition, the LSM gets to forbid keyring searches and key matches.
*
* The search is performed as a breadth-then-depth search up to the prescribed
- * limit (KEYRING_SEARCH_MAX_DEPTH).
+ * limit (KEYRING_SEARCH_MAX_DEPTH). The caller must hold the RCU read lock to
+ * prevent keyrings from being destroyed or rearranged whilst they are being
+ * searched.
*
* Keys are matched to the type provided and are then filtered by the match
* function, which is given the description to use in any way it sees fit. The
@@ -866,7 +868,7 @@ static bool search_nested_keyrings(struct key *keyring,
* In the case of a successful return, the possession attribute from
* @keyring_ref is propagated to the returned key reference.
*/
-key_ref_t keyring_search_aux(key_ref_t keyring_ref,
+key_ref_t keyring_search_rcu(key_ref_t keyring_ref,
struct keyring_search_context *ctx)
{
struct key *keyring;
@@ -888,11 +890,9 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref,
return ERR_PTR(err);
}
- rcu_read_lock();
ctx->now = ktime_get_real_seconds();
if (search_nested_keyrings(keyring, ctx))
__key_get(key_ref_to_ptr(ctx->result));
- rcu_read_unlock();
return ctx->result;
}
@@ -902,7 +902,7 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref,
* @type: The type of keyring we want to find.
* @description: The name of the keyring we want to find.
*
- * As keyring_search_aux() above, but using the current task's credentials and
+ * As keyring_search_rcu() above, but using the current task's credentials and
* type's default matching function and preferred search method.
*/
key_ref_t keyring_search(key_ref_t keyring,
@@ -928,7 +928,9 @@ key_ref_t keyring_search(key_ref_t keyring,
return ERR_PTR(ret);
}
- key = keyring_search_aux(keyring, &ctx);
+ rcu_read_lock();
+ key = keyring_search_rcu(keyring, &ctx);
+ rcu_read_unlock();
if (type->match_free)
type->match_free(&ctx.match_data);
diff --git a/security/keys/proc.c b/security/keys/proc.c
index 78ac305d715e..f081dceae3b9 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -179,7 +179,9 @@ static int proc_keys_show(struct seq_file *m, void *v)
* skip if the key does not indicate the possessor can view it
*/
if (key->perm & KEY_POS_VIEW) {
- skey_ref = search_my_process_keyrings(&ctx);
+ rcu_read_lock();
+ 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);
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 39aaa21462bf..f8ffb06d0297 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -318,7 +318,8 @@ void key_fsgid_changed(struct cred *new_cred)
/*
* Search the process keyrings attached to the supplied cred for the first
- * matching key.
+ * matching key under RCU conditions (the caller must be holding the RCU read
+ * lock).
*
* The search criteria are the type and the match function. The description is
* given to the match function as a parameter, but doesn't otherwise influence
@@ -337,7 +338,7 @@ void key_fsgid_changed(struct cred *new_cred)
* In the case of a successful return, the possession attribute is set on the
* returned key reference.
*/
-key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
+key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx)
{
key_ref_t key_ref, ret, err;
const struct cred *cred = ctx->cred;
@@ -355,7 +356,7 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
/* search the thread keyring first */
if (cred->thread_keyring) {
- key_ref = keyring_search_aux(
+ key_ref = keyring_search_rcu(
make_key_ref(cred->thread_keyring, 1), ctx);
if (!IS_ERR(key_ref))
goto found;
@@ -373,7 +374,7 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
/* search the process keyring second */
if (cred->process_keyring) {
- key_ref = keyring_search_aux(
+ key_ref = keyring_search_rcu(
make_key_ref(cred->process_keyring, 1), ctx);
if (!IS_ERR(key_ref))
goto found;
@@ -394,7 +395,7 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
/* search the session keyring */
if (cred->session_keyring) {
- key_ref = keyring_search_aux(
+ key_ref = keyring_search_rcu(
make_key_ref(cred->session_keyring, 1), ctx);
if (!IS_ERR(key_ref))
@@ -415,7 +416,7 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
}
/* or search the user-session keyring */
else if (READ_ONCE(cred->user->session_keyring)) {
- key_ref = keyring_search_aux(
+ key_ref = keyring_search_rcu(
make_key_ref(READ_ONCE(cred->user->session_keyring), 1),
ctx);
if (!IS_ERR(key_ref))
@@ -448,16 +449,16 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
* the keys attached to the assumed authorisation key using its credentials if
* one is available.
*
- * Return same as search_my_process_keyrings().
+ * The caller must be holding the RCU read lock.
+ *
+ * Return same as search_cred_keyrings_rcu().
*/
-key_ref_t search_process_keyrings(struct keyring_search_context *ctx)
+key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx)
{
struct request_key_auth *rka;
key_ref_t key_ref, ret = ERR_PTR(-EACCES), err;
- might_sleep();
-
- key_ref = search_my_process_keyrings(ctx);
+ key_ref = search_cred_keyrings_rcu(ctx);
if (!IS_ERR(key_ref))
goto found;
err = key_ref;
@@ -472,24 +473,17 @@ key_ref_t search_process_keyrings(struct keyring_search_context *ctx)
) {
const struct cred *cred = ctx->cred;
- /* defend against the auth key being revoked */
- down_read(&cred->request_key_auth->sem);
-
- if (key_validate(ctx->cred->request_key_auth) == 0) {
+ if (key_validate(cred->request_key_auth) == 0) {
rka = ctx->cred->request_key_auth->payload.data[0];
+ //// was search_process_keyrings() [ie. recursive]
ctx->cred = rka->cred;
- key_ref = search_process_keyrings(ctx);
+ key_ref = search_cred_keyrings_rcu(ctx);
ctx->cred = cred;
- up_read(&cred->request_key_auth->sem);
-
if (!IS_ERR(key_ref))
goto found;
-
ret = key_ref;
- } else {
- up_read(&cred->request_key_auth->sem);
}
}
@@ -504,7 +498,6 @@ key_ref_t search_process_keyrings(struct keyring_search_context *ctx)
found:
return key_ref;
}
-
/*
* See if the key we're looking at is the target key.
*/
@@ -691,7 +684,9 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
ctx.index_key = key->index_key;
ctx.match_data.raw_data = key;
kdebug("check possessed");
- skey_ref = search_process_keyrings(&ctx);
+ rcu_read_lock();
+ skey_ref = search_process_keyrings_rcu(&ctx);
+ rcu_read_unlock();
kdebug("possessed=%p", skey_ref);
if (!IS_ERR(skey_ref)) {
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 244e538d113f..bf1d223ec21c 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -385,7 +385,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
* waited for locks */
mutex_lock(&key_construction_mutex);
- key_ref = search_process_keyrings(ctx);
+ rcu_read_lock();
+ key_ref = search_process_keyrings_rcu(ctx);
+ rcu_read_unlock();
if (!IS_ERR(key_ref))
goto key_already_present;
@@ -561,7 +563,9 @@ struct key *request_key_and_link(struct key_type *type,
}
/* search all the process keyrings for a key */
- key_ref = search_process_keyrings(&ctx);
+ rcu_read_lock();
+ key_ref = search_process_keyrings_rcu(&ctx);
+ rcu_read_unlock();
if (!IS_ERR(key_ref)) {
if (dest_keyring) {
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index ec5226557023..99ed7a8a273d 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -58,7 +58,7 @@ static void request_key_auth_free_preparse(struct key_preparsed_payload *prep)
static int request_key_auth_instantiate(struct key *key,
struct key_preparsed_payload *prep)
{
- key->payload.data[0] = (struct request_key_auth *)prep->data;
+ rcu_assign_keypointer(key, (struct request_key_auth *)prep->data);
return 0;
}
@@ -68,7 +68,7 @@ static int request_key_auth_instantiate(struct key *key,
static void request_key_auth_describe(const struct key *key,
struct seq_file *m)
{
- struct request_key_auth *rka = get_request_key_auth(key);
+ struct request_key_auth *rka = dereference_key_rcu(key);
seq_puts(m, "key:");
seq_puts(m, key->description);
@@ -83,7 +83,7 @@ static void request_key_auth_describe(const struct key *key,
static long request_key_auth_read(const struct key *key,
char __user *buffer, size_t buflen)
{
- struct request_key_auth *rka = get_request_key_auth(key);
+ struct request_key_auth *rka = dereference_key_locked(key);
size_t datalen;
long ret;
@@ -102,23 +102,6 @@ static long request_key_auth_read(const struct key *key,
return ret;
}
-/*
- * Handle revocation of an authorisation token key.
- *
- * Called with the key sem write-locked.
- */
-static void request_key_auth_revoke(struct key *key)
-{
- struct request_key_auth *rka = get_request_key_auth(key);
-
- kenter("{%d}", key->serial);
-
- if (rka->cred) {
- put_cred(rka->cred);
- rka->cred = NULL;
- }
-}
-
static void free_request_key_auth(struct request_key_auth *rka)
{
if (!rka)
@@ -131,16 +114,43 @@ static void free_request_key_auth(struct request_key_auth *rka)
kfree(rka);
}
+/*
+ * Dispose of the request_key_auth record under RCU conditions
+ */
+static void request_key_auth_rcu_disposal(struct rcu_head *rcu)
+{
+ struct request_key_auth *rka =
+ container_of(rcu, struct request_key_auth, rcu);
+
+ free_request_key_auth(rka);
+}
+
+/*
+ * Handle revocation of an authorisation token key.
+ *
+ * Called with the key sem write-locked.
+ */
+static void request_key_auth_revoke(struct key *key)
+{
+ struct request_key_auth *rka = dereference_key_locked(key);
+
+ kenter("{%d}", key->serial);
+ rcu_assign_keypointer(key, NULL);
+ call_rcu(&rka->rcu, request_key_auth_rcu_disposal);
+}
+
/*
* Destroy an instantiation authorisation token key.
*/
static void request_key_auth_destroy(struct key *key)
{
- struct request_key_auth *rka = get_request_key_auth(key);
+ struct request_key_auth *rka = rcu_access_pointer(key->payload.rcu_data0);
kenter("{%d}", key->serial);
-
- free_request_key_auth(rka);
+ if (rka) {
+ rcu_assign_keypointer(key, NULL);
+ call_rcu(&rka->rcu, request_key_auth_rcu_disposal);
+ }
}
/*
@@ -249,7 +259,9 @@ struct key *key_get_instantiation_authkey(key_serial_t target_id)
ctx.index_key.desc_len = sprintf(description, "%x", target_id);
- authkey_ref = search_process_keyrings(&ctx);
+ rcu_read_lock();
+ authkey_ref = search_process_keyrings_rcu(&ctx);
+ rcu_read_unlock();
if (IS_ERR(authkey_ref)) {
authkey = ERR_CAST(authkey_ref);
^ permalink raw reply related
* [PATCH 2/6] keys: Invalidate used request_key authentication keys [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
Invalidate used request_key authentication keys rather than revoking them
so that they get cleaned up immediately rather than potentially hanging
around. There doesn't seem any need to keep the revoked keys around.
Signed-off-by: David Howells <dhowells@redhat.com>
---
security/keys/key.c | 4 ++--
security/keys/request_key.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/security/keys/key.c b/security/keys/key.c
index bba71acec886..e792d65c0af8 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -459,7 +459,7 @@ static int __key_instantiate_and_link(struct key *key,
/* disable the authorisation key */
if (authkey)
- key_revoke(authkey);
+ key_invalidate(authkey);
if (prep->expiry != TIME64_MAX) {
key->expiry = prep->expiry;
@@ -616,7 +616,7 @@ int key_reject_and_link(struct key *key,
/* disable the authorisation key */
if (authkey)
- key_revoke(authkey);
+ key_invalidate(authkey);
}
mutex_unlock(&key_construction_mutex);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index a6543ed98b1f..244e538d113f 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -222,7 +222,7 @@ static int construct_key(struct key *key, const void *callout_info,
/* check that the actor called complete_request_key() prior to
* returning an error */
WARN_ON(ret < 0 &&
- !test_bit(KEY_FLAG_REVOKED, &authkey->flags));
+ !test_bit(KEY_FLAG_INVALIDATED, &authkey->flags));
key_put(authkey);
kleave(" = %d", ret);
^ permalink raw reply related
* [PATCH 1/6] keys: Fix request_key() lack of Link perm check on found key [ver #2]
From: David Howells @ 2019-06-19 15:36 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
In-Reply-To: <156095855610.25264.16666970456822465537.stgit@warthog.procyon.org.uk>
The request_key() syscall allows a process to gain access to the 'possessor'
permits of any key that grants it Search permission by virtue of request_key()
not checking whether a key it finds grants Link permission to the caller.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 4 ++++
security/keys/request_key.c | 10 ++++++++++
2 files changed, 14 insertions(+)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 823d29bf44f7..82dd457ff78d 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -433,6 +433,10 @@ The main syscalls are:
/sbin/request-key will be invoked in an attempt to obtain a key. The
callout_info string will be passed as an argument to the program.
+ To link a key into the destination keyring the key must grant link
+ permission on the key to the caller and the keyring must grant write
+ permission.
+
See also Documentation/security/keys/request-key.rst.
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 857da65e1940..a6543ed98b1f 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -564,6 +564,16 @@ struct key *request_key_and_link(struct key_type *type,
key_ref = search_process_keyrings(&ctx);
if (!IS_ERR(key_ref)) {
+ if (dest_keyring) {
+ ret = key_task_permission(key_ref, current_cred(),
+ KEY_NEED_LINK);
+ if (ret < 0) {
+ key_ref_put(key_ref);
+ key = ERR_PTR(ret);
+ goto error_free;
+ }
+ }
+
key = key_ref_to_ptr(key_ref);
if (dest_keyring) {
ret = key_link(dest_keyring, key);
^ permalink raw reply related
* [PATCH 0/6] keys: request_key() improvements [ver #2]
From: David Howells @ 2019-06-19 15:35 UTC (permalink / raw)
To: keyrings
Cc: dhowells, linux-afs, linux-fsdevel, linux-security-module,
linux-kernel
Here's a fix and some improvements for request_key() intended for the next
merge window:
(1) Fix the lack of a Link permission check on a key found by
request_key(), thereby enabling request_key() to link keys that don't
grant this permission to the target keyring (which must still grant
Write permission).
Note that the key must be in the caller's keyrings already to be
found.
(2) Invalidate used request_key authentication keys rather than revoking
them, so that they get cleaned up immediately rather than hanging
around till the expiry time is passed.
(3) Move the RCU locks outwards from the keyring search functions so that
a request_key_rcu() can be provided. This can be called in RCU mode,
so it can't sleep and can't upcall - but it can be called from
LOOKUP_RCU pathwalk mode.
(4) Cache the latest positive result of request_key*() temporarily in
task_struct so that filesystems that make a lot of request_key() calls
during pathwalk can take advantage of it to avoid having to redo the
searching.
It is assumed that the key just found is likely to be used multiple
times in each step in an RCU pathwalk, and is likely to be reused for
the next step too.
Note that the cleanup of the cache is done on TIF_NOTIFY_RESUME, just
before userspace resumes, and on exit.
The patches can be found on the following branch:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-request
and this depends on keys-misc.
David
---
David Howells (6):
keys: Fix request_key() lack of Link perm check on found key
keys: Invalidate used request_key authentication keys
keys: Move the RCU locks outwards from the keyring search functions
keys: Provide request_key_rcu()
keys: Cache result of request_key*() temporarily in task_struct
keys: Kill off request_key_async{,_with_auxdata}
Documentation/security/keys/core.rst | 38 ++-----
Documentation/security/keys/request-key.rst | 33 +++----
include/keys/request_key_auth-type.h | 1
include/linux/key.h | 14 +--
include/linux/sched.h | 5 +
include/linux/tracehook.h | 7 +
kernel/cred.c | 9 ++
security/keys/Kconfig | 17 +++
security/keys/internal.h | 6 +
security/keys/key.c | 4 -
security/keys/keyring.c | 16 ++-
security/keys/proc.c | 4 +
security/keys/process_keys.c | 41 ++++----
security/keys/request_key.c | 137 ++++++++++++++++++---------
security/keys/request_key_auth.c | 60 +++++++-----
15 files changed, 228 insertions(+), 164 deletions(-)
^ permalink raw reply
* Re: [PATCH v2 15/25] LSM: Specify which LSM to display
From: Casey Schaufler @ 2019-06-19 15:33 UTC (permalink / raw)
To: Kees Cook
Cc: casey.schaufler, jmorris, linux-security-module, selinux,
john.johansen, penguin-kernel, paul, sds, casey
In-Reply-To: <201906182127.073A9D7@keescook>
On 6/18/2019 9:33 PM, Kees Cook wrote:
> On Tue, Jun 18, 2019 at 04:05:41PM -0700, Casey Schaufler wrote:
>> Create a new entry "display" in /proc/.../attr for controlling
>> which LSM security information is displayed for a process.
>> The name of an active LSM that supplies hooks for human readable
>> data may be written to "display" to set the value. The name of
>> the LSM currently in use can be read from "display".
>> At this point there can only be one LSM capable of display
>> active.
>>
>> This affects /proc/.../attr/current and SO_PEERSEC.
> What happened to creating /proc/$pid/lsm/$lsm_name/current for "modern"
> LSM libraries to start using (instead of possibly fighting over the
> /proc/$pid/attr/display)?
Smack already has it and the mechanics are available for
anyone who wants to use it. John says that AppArmor is moving
away from using the attr interfaces. Stephen and Paul have been
silent on the topic, but my assumption is that the SELinux
attitude is "I had them first, it's not my problem". When
we get around to creating liblsm, with the "real" LSM user
space APIs it will probably drive the addition of more
/proc/$pid/lsm/$lsm_name directories.
> (Obviously "display" is needed for "old"
> libraries, and I'm fine with it.)
Yes, and we can expect some distos to cling to the
old libraries for a looooong time.
> Similarly, is there something that can be done for SO_PEERSEC that
> doesn't require using "display" for "modern" libraries?
Yes, and I made sure the implementation could
accommodate that. It would be easy to add a "display"
that would use the much discussed $lsm1="a",$lsm2="b"
format. I didn't include it because without a liblsm to
use it it's just clutter.
>
> -Kees
>
>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>> ---
>> fs/proc/base.c | 1 +
>> security/security.c | 108 +++++++++++++++++++++++++++++++++++---------
>> 2 files changed, 88 insertions(+), 21 deletions(-)
>>
>> diff --git a/fs/proc/base.c b/fs/proc/base.c
>> index ddef482f1334..7bf70e041315 100644
>> --- a/fs/proc/base.c
>> +++ b/fs/proc/base.c
>> @@ -2618,6 +2618,7 @@ static const struct pid_entry attr_dir_stuff[] = {
>> ATTR(NULL, "fscreate", 0666),
>> ATTR(NULL, "keycreate", 0666),
>> ATTR(NULL, "sockcreate", 0666),
>> + ATTR(NULL, "display", 0666),
>> #ifdef CONFIG_SECURITY_SMACK
>> DIR("smack", 0555,
>> proc_smack_attr_dir_inode_ops, proc_smack_attr_dir_ops),
>> diff --git a/security/security.c b/security/security.c
>> index 46f6cf21d33c..9cfdc664239e 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -46,7 +46,9 @@ static struct kmem_cache *lsm_file_cache;
>> static struct kmem_cache *lsm_inode_cache;
>>
>> char *lsm_names;
>> -static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init;
>> +static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init = {
>> + .lbs_task = sizeof(int),
>> +};
>>
>> /* Boot-time LSM user choice */
>> static __initdata const char *chosen_lsm_order;
>> @@ -578,6 +580,8 @@ int lsm_inode_alloc(struct inode *inode)
>> */
>> static int lsm_task_alloc(struct task_struct *task)
>> {
>> + int *display;
>> +
>> if (blob_sizes.lbs_task == 0) {
>> task->security = NULL;
>> return 0;
>> @@ -586,6 +590,10 @@ static int lsm_task_alloc(struct task_struct *task)
>> task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
>> if (task->security == NULL)
>> return -ENOMEM;
>> +
>> + display = task->security;
>> + *display = LSMDATA_INVALID;
>> +
>> return 0;
>> }
>>
>> @@ -1574,14 +1582,27 @@ int security_file_open(struct file *file)
>>
>> int security_task_alloc(struct task_struct *task, unsigned long clone_flags)
>> {
>> + int *odisplay = current->security;
>> + int *ndisplay;
>> int rc = lsm_task_alloc(task);
>>
>> - if (rc)
>> + if (unlikely(rc))
>> return rc;
>> +
>> rc = call_int_hook(task_alloc, 0, task, clone_flags);
>> - if (unlikely(rc))
>> + if (unlikely(rc)) {
>> security_task_free(task);
>> - return rc;
>> + return rc;
>> + }
>> +
>> + ndisplay = task->security;
>> + if (ndisplay == NULL)
>> + return 0;
>> +
>> + if (odisplay != NULL)
>> + *ndisplay = *odisplay;
>> +
>> + return 0;
>> }
>>
>> void security_task_free(struct task_struct *task)
>> @@ -1967,10 +1988,28 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
>> char **value)
>> {
>> struct security_hook_list *hp;
>> + int *display = current->security;
>> +
>> + if (!strcmp(name, "display")) {
>> + hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx,
>> + list) {
>> + if (*display == LSMDATA_INVALID ||
>> + hp->slot == *display) {
>> + *value = kstrdup(hp->lsm, GFP_KERNEL);
>> + if (*value)
>> + return strlen(hp->lsm);
>> + return -ENOMEM;
>> + }
>> + }
>> + return -EINVAL;
>> + }
>>
>> hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
>> if (lsm != NULL && strcmp(lsm, hp->lsm))
>> continue;
>> + if (lsm == NULL && *display != LSMDATA_INVALID &&
>> + *display != hp->slot)
>> + continue;
>> return hp->hook.getprocattr(p, name, value);
>> }
>> return -EINVAL;
>> @@ -1980,10 +2019,27 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
>> size_t size)
>> {
>> struct security_hook_list *hp;
>> + int *display = current->security;
>> + int len;
>> +
>> + if (!strcmp(name, "display")) {
>> + hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx,
>> + list) {
>> + len = strlen(hp->lsm);
>> + if (size >= len && !strncmp(value, hp->lsm, len)) {
>> + *display = hp->slot;
>> + return size;
>> + }
>> + }
>> + return -EINVAL;
>> + }
>>
>> hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
>> if (lsm != NULL && strcmp(lsm, hp->lsm))
>> continue;
>> + if (lsm == NULL && *display != LSMDATA_INVALID &&
>> + *display != hp->slot)
>> + continue;
>> return hp->hook.setprocattr(name, value, size);
>> }
>> return -EINVAL;
>> @@ -2002,38 +2058,41 @@ EXPORT_SYMBOL(security_ismaclabel);
>>
>> int security_secid_to_secctx(struct lsmblob *l, char **secdata, u32 *seclen)
>> {
>> + int *display = current->security;
>> struct security_hook_list *hp;
>> - int rc;
>>
>> - hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list) {
>> - rc = hp->hook.secid_to_secctx(l->secid[hp->slot],
>> - secdata, seclen);
>> - if (rc != 0)
>> - return rc;
>> - }
>> + hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list)
>> + if (*display == LSMDATA_INVALID || *display == hp->slot)
>> + return hp->hook.secid_to_secctx(l->secid[hp->slot],
>> + secdata, seclen);
>> return 0;
>> }
>> EXPORT_SYMBOL(security_secid_to_secctx);
>>
>> int security_secctx_to_secid(const char *secdata, u32 seclen, struct lsmblob *l)
>> {
>> + int *display = current->security;
>> struct security_hook_list *hp;
>> - int rc;
>>
>> lsmblob_init(l, 0);
>> - hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list) {
>> - rc = hp->hook.secctx_to_secid(secdata, seclen,
>> - &l->secid[hp->slot]);
>> - if (rc != 0)
>> - return rc;
>> - }
>> + hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list)
>> + if (*display == LSMDATA_INVALID || *display == hp->slot)
>> + return hp->hook.secctx_to_secid(secdata, seclen,
>> + &l->secid[hp->slot]);
>> return 0;
>> }
>> EXPORT_SYMBOL(security_secctx_to_secid);
>>
>> void security_release_secctx(char *secdata, u32 seclen)
>> {
>> - call_void_hook(release_secctx, secdata, seclen);
>> + int *display = current->security;
>> + struct security_hook_list *hp;
>> +
>> + hlist_for_each_entry(hp, &security_hook_heads.release_secctx, list)
>> + if (*display == LSMDATA_INVALID || *display == hp->slot) {
>> + hp->hook.release_secctx(secdata, seclen);
>> + return;
>> + }
>> }
>> EXPORT_SYMBOL(security_release_secctx);
>>
>> @@ -2158,8 +2217,15 @@ EXPORT_SYMBOL(security_sock_rcv_skb);
>> int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
>> int __user *optlen, unsigned len)
>> {
>> - return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
>> - optval, optlen, len);
>> + int *display = current->security;
>> + struct security_hook_list *hp;
>> +
>> + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream,
>> + list)
>> + if (*display == LSMDATA_INVALID || *display == hp->slot)
>> + return hp->hook.socket_getpeersec_stream(sock, optval,
>> + optlen, len);
>> + return -ENOPROTOOPT;
>> }
>>
>> int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
>> --
>> 2.20.1
>>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox