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

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 (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 <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);
+}

Reply via email to