[RFC,19/27] containers: Sample: request_key upcall handling
diff mbox series

Message ID 155024703003.21651.3499235528404179500.stgit@warthog.procyon.org.uk
State New
Headers show
Series
  • Containers and using authenticated filesystems
Related show

Commit Message

David Howells Feb. 15, 2019, 4:10 p.m. UTC
Implement a sample upcall handling.

Firstly, the test-container sample is modified to (a) create a staging
keyring and to (b) intercept request_key calls for user-type keys inside
the container and place the authentication keys into that rather than
invoking /sbin/request-key.

Secondly, a test-upcall sample is added that will monitor the keyring for
notifications and spawn /sbin/request-key instances for each of key added.
This is run as:

	./test-upcall

to find a keyring called "upcall" in the session keyring (as created by the
./test-container program) and listen for additions to that, or it can be
run as:

	./test-upcall <keyring-id>

to listen on a specific keyring.

Note that the test-upcall sample is designed to be run separately from
test-container so that its stdout can be observed.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 samples/vfs/Makefile         |    6 +
 samples/vfs/test-container.c |   16 +++
 samples/vfs/test-upcall.c    |  243 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 264 insertions(+), 1 deletion(-)
 create mode 100644 samples/vfs/test-upcall.c

Patch
diff mbox series

diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
index 25420919ee40..a8e9e1142ae3 100644
--- a/samples/vfs/Makefile
+++ b/samples/vfs/Makefile
@@ -5,7 +5,8 @@  hostprogs-$(CONFIG_SAMPLE_VFS) := \
 	test-fsmount \
 	test-mntinfo \
 	test-statx \
-	test-container
+	test-container \
+	test-upcall
 
 # Tell kbuild to always build the programs
 always := $(hostprogs-y)
@@ -18,5 +19,8 @@  HOSTLDLIBS_test-mntinfo += -lm
 HOSTCFLAGS_test-fs-query.o += -I$(objtree)/usr/include
 HOSTCFLAGS_test-fsmount.o += -I$(objtree)/usr/include
 HOSTCFLAGS_test-statx.o += -I$(objtree)/usr/include
+
 HOSTCFLAGS_test-container.o += -I$(objtree)/usr/include
 HOSTLDLIBS_test-container += -lkeyutils
+HOSTCFLAGS_test-upcall.o += -I$(objtree)/usr/include
+HOSTLDLIBS_test-upcall += -lkeyutils
diff --git a/samples/vfs/test-container.c b/samples/vfs/test-container.c
index 44ff57afb5a4..7dc9071399b2 100644
--- a/samples/vfs/test-container.c
+++ b/samples/vfs/test-container.c
@@ -20,6 +20,8 @@ 
 #include <sys/stat.h>
 #include <keyutils.h>
 
+#define KEYCTL_CONTAINER_INTERCEPT	31	/* Intercept upcalls inside a container */
+
 /* Hope -1 isn't a syscall */
 #ifndef __NR_fsopen
 #define __NR_fsopen -1
@@ -187,6 +189,7 @@  void container_init(void)
  */
 int main(int argc, char *argv[])
 {
+	key_serial_t keyring;
 	pid_t pid;
 	int fsfd, mfd, cfd, ws;
 
@@ -259,6 +262,19 @@  int main(int argc, char *argv[])
 	E(close(fsfd));
 	E(close(mfd));
 
+	/* Create a keyring to catch upcalls. */
+	printf("Intercepting...\n");
+	keyring = add_key("keyring", "upcall", NULL, 0, KEY_SPEC_SESSION_KEYRING);
+	if (keyring == -1) {
+		perror("add_key/u");
+		exit(1);
+	}
+
+	if (keyctl(KEYCTL_CONTAINER_INTERCEPT, cfd, "user", 0, keyring) < 0) {
+		perror("keyctl_container_intercept");
+		exit(1);
+	}
+
 	/* Start the 'init' process. */
 	printf("Forking...\n");
 	switch ((pid = fork_into_container(cfd))) {
diff --git a/samples/vfs/test-upcall.c b/samples/vfs/test-upcall.c
new file mode 100644
index 000000000000..225fa0325d1b
--- /dev/null
+++ b/samples/vfs/test-upcall.c
@@ -0,0 +1,243 @@ 
+/* Container keyring upcall management test.
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <keyutils.h>
+#include <linux/watch_queue.h>
+
+#define KEYCTL_WATCH_KEY		30	/* Watch a key or ring of keys for changes */
+#define KEYCTL_QUERY_REQUEST_KEY_AUTH	32	/* Query a request_key_auth key */
+#define KEYCTL_MOVE			33	/* Move keys between keyrings */
+#define KEYCTL_FIND_LRU			34	/* Find the least-recently used key in a keyring */
+
+struct keyctl_query_request_key_auth {
+	char		operation[32];	/* Operation name, typically "create" */
+	uid_t		fsuid;		/* UID of requester */
+	gid_t		fsgid;		/* GID of requester */
+	key_serial_t	target_key;	/* The key being instantiated */
+	key_serial_t	thread_keyring;	/* The requester's thread keyring */
+	key_serial_t	process_keyring; /* The requester's process keyring */
+	key_serial_t	session_keyring; /* The requester's session keyring */
+	long long	spare[1];
+};
+
+static void process_request(key_serial_t keyring, key_serial_t key)
+{
+	struct keyctl_query_request_key_auth info;
+	char target[32], uid[32], gid[32], thread[32], process[32], session[32];
+	void *callout;
+	long len;
+
+#if 0
+	key = keyctl(KEYCTL_FIND_LRU, keyring, ".request_key_auth");
+	if (key == -1) {
+		perror("keyctl/find");
+		exit(1);
+	}
+#endif
+
+	if (keyctl(KEYCTL_QUERY_REQUEST_KEY_AUTH, key, &info) == -1) {
+		perror("keyctl/query");
+		exit(1);
+	}
+
+	len = keyctl_read_alloc(key, &callout);
+	if (len == -1) {
+		perror("keyctl/read");
+		exit(1);
+	}
+
+	sprintf(target, "%d", info.target_key);
+	sprintf(uid, "%d", info.fsuid);
+	sprintf(gid, "%d", info.fsgid);
+	sprintf(thread, "%d", info.thread_keyring);
+	sprintf(process, "%d", info.process_keyring);
+	sprintf(session, "%d", info.session_keyring);
+
+	printf("Authentication key %d\n", key);
+	printf("- %s %s\n", info.operation, target);
+	printf("- uid=%s gid=%s\n", uid, gid);
+	printf("- rings=%s,%s,%s\n", thread, process, session);
+	printf("- callout='%s'\n", (char *)callout);
+
+	switch (fork()) {
+	case 0:
+		/* Only pass the auth token of interest onto /sbin/request-key */
+		if (keyctl(KEYCTL_MOVE, key, keyring, KEY_SPEC_THREAD_KEYRING) < 0) {
+			perror("keyctl_move/1");
+			exit(1);
+		}
+
+		if (keyctl_join_session_keyring(NULL) < 0) {
+			perror("keyctl_join");
+			exit(1);
+		}
+
+		if (keyctl(KEYCTL_MOVE, key,
+			   KEY_SPEC_THREAD_KEYRING, KEY_SPEC_SESSION_KEYRING) < 0) {
+			perror("keyctl_move/2");
+			exit(1);
+		}
+
+		execl("/sbin/request-key",
+		      "request-key", info.operation, target, uid, gid, thread, process, session,
+		      NULL);
+		perror("execve");
+		exit(1);
+
+	case -1:
+		perror("fork");
+		exit(1);
+
+	default:
+		return;
+	}
+}
+
+/*
+ * We saw a change on the keyring.
+ */
+static void saw_key_change(struct watch_notification *n)
+{
+	struct key_notification *k = (struct key_notification *)n;
+	unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+	if (len != sizeof(struct key_notification))
+		return;
+
+	printf("KEY %d change=%u aux=%d\n", k->key_id, n->subtype, k->aux);
+
+	process_request(k->key_id, k->aux);
+}
+
+/*
+ * Consume and display events.
+ */
+static int consumer(int fd, struct watch_queue_buffer *buf)
+{
+	struct watch_notification *n;
+	struct pollfd p[1];
+	unsigned int head, tail, mask = buf->meta.mask;
+
+	for (;;) {
+		p[0].fd = fd;
+		p[0].events = POLLIN | POLLERR;
+		p[0].revents = 0;
+
+		if (poll(p, 1, -1) == -1) {
+			perror("poll");
+			break;
+		}
+
+		printf("ptrs h=%x t=%x m=%x\n",
+		       buf->meta.head, buf->meta.tail, buf->meta.mask);
+
+		while (head = __atomic_load_n(&buf->meta.head, __ATOMIC_ACQUIRE),
+		       tail = buf->meta.tail,
+		       tail != head
+		       ) {
+			n = &buf->slots[tail & mask];
+			printf("NOTIFY[%08x-%08x] ty=%04x sy=%04x i=%08x\n",
+			       head, tail, n->type, n->subtype, n->info);
+			if ((n->info & WATCH_INFO_LENGTH) == 0)
+				goto out;
+
+			switch (n->type) {
+			case WATCH_TYPE_META:
+				if (n->subtype == WATCH_META_REMOVAL_NOTIFICATION)
+					printf("REMOVAL of watchpoint %08x\n",
+					       n->info & WATCH_INFO_ID);
+				break;
+			case WATCH_TYPE_KEY_NOTIFY:
+				saw_key_change(n);
+				break;
+			}
+
+			tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+			__atomic_store_n(&buf->meta.tail, tail, __ATOMIC_RELEASE);
+		}
+	}
+
+out:
+	return 0;
+}
+
+/*
+ * We're only interested in key insertion events.
+ */
+static struct watch_notification_filter filter = {
+	.nr_filters	= 1,
+	.filters = {
+		[0] = {
+			.type			= WATCH_TYPE_KEY_NOTIFY,
+			.subtype_filter[0]	= (1 << NOTIFY_KEY_LINKED),
+		},
+	}
+};
+
+int main(int argc, char *argv[])
+{
+	struct watch_queue_buffer *buf;
+	key_serial_t keyring;
+	size_t page_size = sysconf(_SC_PAGESIZE);
+	int fd;
+
+	if (argc == 1) {
+		keyring = keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring",
+					"upcall", 0);
+		if (keyring == -1) {
+			perror("keyctl_search");
+			exit(1);
+		}
+	} else if (argc == 2) {
+		keyring = strtoul(argv[1], NULL, 0);
+	} else {
+		fprintf(stderr, "Format: test-upcall [<keyring>]\n");
+		exit(2);
+	}
+
+	/* Create a watch on the keyring to detect the addition of keys. */
+	fd = open("/dev/watch_queue", O_RDWR | O_CLOEXEC);
+	if (fd == -1) {
+		perror("/dev/watch_queue");
+		exit(1);
+	}
+
+	if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, 1) == -1) {
+		perror("/dev/watch_queue(size)");
+		exit(1);
+	}
+
+	if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) {
+		perror("/dev/watch_queue(filter)");
+		exit(1);
+	}
+
+	buf = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (buf == MAP_FAILED) {
+		perror("mmap");
+		exit(1);
+	}
+
+	if (keyctl(KEYCTL_WATCH_KEY, keyring, fd, 0x01) == -1) {
+		perror("keyctl");
+		exit(1);
+	}
+
+	return consumer(fd, buf);
+}