lkml.org 
[lkml]   [2019]   [Feb]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Subject[RFC PATCH 12/27] containers: Allow a daemon to intercept request_key upcalls in a container
    From
    Date
    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@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 (dhowells@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;

    \
     
     \ /
      Last update: 2019-02-15 17:09    [W:2.790 / U:0.108 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site