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 <dhowe...@redhat.com>
---

 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 (dhowe...@redhat.com)
+ *
+ * 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;
 

Reply via email to