diff mbox series

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

Message ID 155024695603.21651.8408384800355761335.stgit@warthog.procyon.org.uk (mailing list archive)
State New, archived
Headers show
Series Containers and using authenticated filesystems | expand

Commit Message

David Howells Feb. 15, 2019, 4:09 p.m. UTC
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 mbox series

Patch

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;