[RFC PATCH 12/27] containers: Allow a daemon to intercept request_key upcalls in a container

From: David Howells
Date: Fri Feb 15 2019 - 11:09:24 EST


Provide a mechanism by which a running daemon can intercept request_key
upcalls, filtered by namespace and key type, and service them. The list of
active services is per-container.

Intercepts for a specific {key_type, namespace} can be installed on a
container with:

keyctl(KEYCTL_ADD_UPCALL_INTERCEPT,
int containerfd,
const char *type_name,
unsigned int ns_id,
key_serial_t dest_keyring);

The authentication token keys for intercepted keys are linked into the
destination keyring.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

include/linux/container.h | 2
include/linux/key-type.h | 2
include/uapi/linux/keyctl.h | 1
kernel/container.c | 4 +
security/keys/Makefile | 2
security/keys/compat.c | 5 +
security/keys/container.c | 227 ++++++++++++++++++++++++++++++++++++++
security/keys/internal.h | 10 ++
security/keys/keyctl.c | 14 ++
security/keys/request_key.c | 18 ++-
security/keys/request_key_auth.c | 6 +
11 files changed, 278 insertions(+), 13 deletions(-)
create mode 100644 security/keys/container.c

diff --git a/include/linux/container.h b/include/linux/container.h
index 087aa1885ef7..a8cac800ce75 100644
--- a/include/linux/container.h
+++ b/include/linux/container.h
@@ -42,6 +42,7 @@ struct container {
struct list_head members; /* Member processes, guarded with ->lock */
struct list_head child_link; /* Link in parent->children */
struct list_head children; /* Child containers */
+ struct list_head req_key_traps; /* Traps for request-key upcalls */
wait_queue_head_t waitq; /* Someone waiting for init to exit waits here */
unsigned long flags;
#define CONTAINER_FLAG_INIT_STARTED 0 /* Init is started - certain ops now prohibited */
@@ -60,6 +61,7 @@ extern int copy_container(unsigned long flags, struct task_struct *tsk,
struct container *container);
extern void exit_container(struct task_struct *tsk);
extern void put_container(struct container *c);
+extern long key_del_intercept(struct container *c, const char *type);

static inline struct container *get_container(struct container *c)
{
diff --git a/include/linux/key-type.h b/include/linux/key-type.h
index 2148a6bf58f1..0e09dac53245 100644
--- a/include/linux/key-type.h
+++ b/include/linux/key-type.h
@@ -66,7 +66,7 @@ struct key_match_data {
*/
struct key_type {
/* name of the type */
- const char *name;
+ const char name[24];

/* default payload length for quota precalculation (optional)
* - this can be used instead of calling key_payload_reserve(), that
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index e9e7da849619..85e8fef89bba 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -68,6 +68,7 @@
#define KEYCTL_PKEY_VERIFY 28 /* Verify a public key signature */
#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */
#define KEYCTL_WATCH_KEY 30 /* Watch a key or ring of keys for changes */
+#define KEYCTL_CONTAINER_INTERCEPT 31 /* Intercept upcalls inside a container */

/* keyctl structures */
struct keyctl_dh_params {
diff --git a/kernel/container.c b/kernel/container.c
index 360284db959b..33e41fe5050b 100644
--- a/kernel/container.c
+++ b/kernel/container.c
@@ -35,6 +35,7 @@ struct container init_container = {
.members.next = &init_task.container_link,
.members.prev = &init_task.container_link,
.children = LIST_HEAD_INIT(init_container.children),
+ .req_key_traps = LIST_HEAD_INIT(init_container.req_key_traps),
.flags = (1 << CONTAINER_FLAG_INIT_STARTED),
.lock = __SPIN_LOCK_UNLOCKED(init_container.lock),
.seq = SEQCNT_ZERO(init_fs.seq),
@@ -53,6 +54,8 @@ void put_container(struct container *c)

while (c && refcount_dec_and_test(&c->usage)) {
BUG_ON(!list_empty(&c->members));
+ if (!list_empty(&c->req_key_traps))
+ key_del_intercept(c, NULL);
if (c->pid_ns)
put_pid_ns(c->pid_ns);
if (c->ns)
@@ -286,6 +289,7 @@ static struct container *alloc_container(const char __user *name)

INIT_LIST_HEAD(&c->members);
INIT_LIST_HEAD(&c->children);
+ INIT_LIST_HEAD(&c->req_key_traps);
init_waitqueue_head(&c->waitq);
spin_lock_init(&c->lock);
refcount_set(&c->usage, 1);
diff --git a/security/keys/Makefile b/security/keys/Makefile
index 9cef54064f60..24f5df27b1c2 100644
--- a/security/keys/Makefile
+++ b/security/keys/Makefile
@@ -16,6 +16,7 @@ obj-y := \
request_key.o \
request_key_auth.o \
user_defined.o
+
compat-obj-$(CONFIG_KEY_DH_OPERATIONS) += compat_dh.o
obj-$(CONFIG_KEYS_COMPAT) += compat.o $(compat-obj-y)
obj-$(CONFIG_PROC_FS) += proc.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SYSCTL) += sysctl.o
obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o
obj-$(CONFIG_KEY_DH_OPERATIONS) += dh.o
obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += keyctl_pkey.o
+obj-$(CONFIG_CONTAINERS) += container.o

#
# Key types
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 021d8e1c9233..6420881e5ce7 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -161,6 +161,11 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_WATCH_KEY:
return keyctl_watch_key(arg2, arg3, arg4);

+#ifdef CONFIG_CONTAINERS
+ case KEYCTL_CONTAINER_INTERCEPT:
+ return keyctl_container_intercept(arg2, compat_ptr(arg3), arg4, arg5);
+#endif
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/container.c b/security/keys/container.c
new file mode 100644
index 000000000000..c61c43658f3b
--- /dev/null
+++ b/security/keys/container.c
@@ -0,0 +1,227 @@
+/* Container intercept interface
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@xxxxxxxxxx)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/key.h>
+#include <linux/key-type.h>
+#include <linux/container.h>
+#include <keys/request_key_auth-type.h>
+#include "internal.h"
+
+struct request_key_intercept {
+ char type[32]; /* The type of key to be trapped */
+ struct list_head link; /* Link in containers->req_key_traps */
+ struct key *dest_keyring; /* Where to place the trapped auth keys */
+ struct ns_common *ns; /* Namespace the key must match */
+};
+
+/*
+ * Add an intercept filter to a container.
+ */
+static long key_add_intercept(struct container *c, struct request_key_intercept *rki)
+{
+ struct request_key_intercept *p;
+
+ kenter("%p,{%s,%d}", c, rki->type, key_serial(rki->dest_keyring));
+
+ spin_lock(&c->lock);
+ list_for_each_entry(p, &c->req_key_traps, link) {
+ if (strcmp(rki->type, p->type) == 0) {
+ spin_unlock(&c->lock);
+ return -EEXIST;
+ }
+ }
+
+ /* We put all-matching rules at the back so they're checked after the
+ * more specific rules.
+ */
+ if (rki->type[0] == '*' && !rki->type[1])
+ list_add_tail(&rki->link, &c->req_key_traps);
+ else
+ list_add(&rki->link, &c->req_key_traps);
+
+ spin_unlock(&c->lock);
+ kleave(" = 0");
+ return 0;
+}
+
+/*
+ * Remove one or more intercept filters from a container. Returns the number
+ * of entries removed.
+ */
+long key_del_intercept(struct container *c, const char *type)
+{
+ struct request_key_intercept *p, *q;
+ long count;
+ LIST_HEAD(graveyard);
+
+ kenter("%p,%s", c, type);
+
+ spin_lock(&c->lock);
+ list_for_each_entry_safe(p, q, &c->req_key_traps, link) {
+ if (!type || strcmp(p->type, type) == 0) {
+ kdebug("- match %d", key_serial(p->dest_keyring));
+ list_move(&p->link, &graveyard);
+ }
+ }
+ spin_unlock(&c->lock);
+
+ count = 0;
+ while (!list_empty(&graveyard)) {
+ p = list_entry(graveyard.next, struct request_key_intercept, link);
+ list_del(&p->link);
+ count++;
+
+ key_put(p->dest_keyring);
+ kfree(p);
+ }
+
+ kleave(" = %ld", count);
+ return count;
+}
+
+/*
+ * Create an intercept filter and add it to a container.
+ */
+static long key_create_intercept(struct container *c, const char *type,
+ key_serial_t dest_ring_id)
+{
+ struct request_key_intercept *rki;
+ key_ref_t dest_ref;
+ long ret = -ENOMEM;
+
+ dest_ref = lookup_user_key(dest_ring_id, KEY_LOOKUP_CREATE,
+ KEY_NEED_WRITE);
+ if (IS_ERR(dest_ref))
+ return PTR_ERR(dest_ref);
+
+ rki = kzalloc(sizeof(*rki), GFP_KERNEL);
+ if (!rki)
+ goto out_dest;
+
+ memcpy(rki->type, type, sizeof(rki->type));
+ rki->dest_keyring = key_ref_to_ptr(dest_ref);
+ /* TODO: set rki->ns */
+
+ ret = key_add_intercept(c, rki);
+ if (ret < 0)
+ goto out_rki;
+ return ret;
+
+out_rki:
+ kfree(rki);
+out_dest:
+ key_ref_put(dest_ref);
+ return ret;
+}
+
+/*
+ * Add or remove (if dest_keyring==0) a request_key upcall intercept trap upon
+ * a container. If _type points to a string of "*" that matches all types.
+ */
+long keyctl_container_intercept(int containerfd,
+ const char *_type,
+ unsigned int ns_id,
+ key_serial_t dest_ring_id)
+{
+ struct container *c;
+ struct fd f;
+ char type[32] = "";
+ long ret;
+
+ if (containerfd < 0 || ns_id < 0)
+ return -EINVAL;
+ if (dest_ring_id && !_type)
+ return -EINVAL;
+
+ f = fdget(containerfd);
+ if (!f.file)
+ return -EBADF;
+ ret = -EINVAL;
+ if (!is_container_file(f.file))
+ goto out_fd;
+
+ c = f.file->private_data;
+
+ /* Find out what type we're dealing with (can be NULL to make removal
+ * remove everything).
+ */
+ if (_type) {
+ ret = key_get_type_from_user(type, _type, sizeof(type));
+ if (ret < 0)
+ goto out_fd;
+ }
+
+ /* TODO: Get the namespace to filter on */
+
+ /* We add a filter if a destination keyring has been specified. */
+ if (dest_ring_id) {
+ ret = key_create_intercept(c, type, dest_ring_id);
+ } else {
+ ret = key_del_intercept(c, _type ? type : NULL);
+ }
+
+out_fd:
+ fdput(f);
+ return ret;
+}
+
+/*
+ * Queue a construction record if we can find a handler.
+ *
+ * Returns true if we found a handler - in which case ownership of the
+ * construction record has been passed on to the service queue and the caller
+ * can no longer touch it.
+ */
+int queue_request_key(struct key *authkey)
+{
+ struct container *c = current->container;
+ struct request_key_intercept *rki;
+ struct request_key_auth *rka = get_request_key_auth(authkey);
+ struct key *service_keyring;
+ struct key *key = rka->target_key;
+ int ret;
+
+ kenter("%p,%d,%d", c, key_serial(authkey), key_serial(key));
+
+ if (list_empty(&c->req_key_traps)) {
+ kleave(" = -EAGAIN [e]");
+ return -EAGAIN;
+ }
+
+ spin_lock(&c->lock);
+
+ list_for_each_entry(rki, &c->req_key_traps, link) {
+ if (strcmp(rki->type, "*") == 0 ||
+ strcmp(rki->type, key->type->name) == 0)
+ goto found_match;
+ }
+
+ spin_unlock(&c->lock);
+ kleave(" = -EAGAIN [n]");
+ return -EAGAIN;
+
+found_match:
+ service_keyring = key_get(rki->dest_keyring);
+ kdebug("- match %d", key_serial(service_keyring));
+ spin_unlock(&c->lock);
+
+ /* We add the authentication key to the keyring for the service daemon
+ * to collect. This can be detected by means of a watch on the service
+ * keyring.
+ */
+ ret = key_link(service_keyring, authkey);
+ key_put(service_keyring);
+ kleave(" = %d", ret);
+ return ret;
+}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 14c5b8ad5bd6..e98fca465146 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -93,6 +93,7 @@ extern wait_queue_head_t request_key_conswq;
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);
+extern int key_get_type_from_user(char *, const char __user *, unsigned);

extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
@@ -180,6 +181,11 @@ extern void key_gc_keytype(struct key_type *ktype);
extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
+#ifdef CONFIG_CONTAINERS
+extern int queue_request_key(struct key *);
+#else
+static inline int queue_request_key(struct key *authkey) { return -EAGAIN; }
+#endif

static inline void notify_key(struct key *key,
enum key_notification_subtype subtype, u32 aux)
@@ -354,6 +360,10 @@ static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch
}
#endif

+#ifdef CONFIG_CONTAINERS
+extern long keyctl_container_intercept(int, const char __user *, unsigned int, key_serial_t);
+#endif
+
/*
* Debugging key validation
*/
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 94b99a52b4e5..38ff33431f33 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -30,9 +30,9 @@

#define KEY_MAX_DESC_SIZE 4096

-static int key_get_type_from_user(char *type,
- const char __user *_type,
- unsigned len)
+int key_get_type_from_user(char *type,
+ const char __user *_type,
+ unsigned len)
{
int ret;

@@ -1857,6 +1857,14 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
case KEYCTL_WATCH_KEY:
return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);

+#ifdef CONFIG_CONTAINERS
+ case KEYCTL_CONTAINER_INTERCEPT:
+ return keyctl_container_intercept((int)arg2,
+ (const char __user *)arg3,
+ (unsigned int)arg4,
+ (key_serial_t)arg5);
+#endif
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index edfabf20bdbb..078767564283 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -17,6 +17,7 @@
#include <linux/err.h>
#include <linux/keyctl.h>
#include <linux/slab.h>
+#include <linux/init_task.h>
#include <net/net_namespace.h>
#include "internal.h"
#include <keys/request_key_auth-type.h>
@@ -91,11 +92,11 @@ static int call_usermodehelper_keys(const char *path, char **argv, char **envp,
* Request userspace finish the construction of a key
* - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring>"
*/
-static int call_sbin_request_key(struct key *authkey, void *aux)
+static int call_sbin_request_key(struct key *authkey)
{
static char const request_key[] = "/sbin/request-key";
struct request_key_auth *rka = get_request_key_auth(authkey);
- const struct cred *cred = current_cred();
+ const struct cred *cred = rka->cred;
key_serial_t prkey, sskey;
struct key *key = rka->target_key, *keyring, *session;
char *argv[9], *envp[3], uid_str[12], gid_str[12];
@@ -203,7 +204,6 @@ static int construct_key(struct key *key, const void *callout_info,
size_t callout_len, void *aux,
struct key *dest_keyring)
{
- request_key_actor_t actor;
struct key *authkey;
int ret;

@@ -216,11 +216,13 @@ static int construct_key(struct key *key, const void *callout_info,
return PTR_ERR(authkey);

/* Make the call */
- actor = call_sbin_request_key;
- if (key->type->request_key)
- actor = key->type->request_key;
-
- ret = actor(authkey, aux);
+ if (key->type->request_key) {
+ ret = key->type->request_key(authkey, aux);
+ } else {
+ ret = queue_request_key(authkey);
+ if (ret == -EAGAIN)
+ ret = call_sbin_request_key(authkey);
+ }

/* check that the actor called complete_request_key() prior to
* returning an error */
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index afc304e8b61e..cd75173cadad 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -123,6 +123,10 @@ static void free_request_key_auth(struct request_key_auth *rka)
{
if (!rka)
return;
+
+ if (rka->target_key->state == KEY_IS_UNINSTANTIATED)
+ key_reject_and_link(rka->target_key, 0, -ENOKEY, NULL, NULL);
+
key_put(rka->target_key);
key_put(rka->dest_keyring);
if (rka->cred)
@@ -184,7 +188,7 @@ struct key *request_key_auth_new(struct key *target, const char *op,
goto error_free_rka;
}

- irka = cred->request_key_auth->payload.data[0];
+ irka = get_request_key_auth(cred->request_key_auth);
rka->cred = get_cred(irka->cred);
rka->pid = irka->pid;