diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | keyctl.c | 13 | ||||
-rw-r--r-- | keyctl.h | 13 | ||||
-rw-r--r-- | keyctl_watch.c | 528 | ||||
-rw-r--r-- | keyutils.c | 5 | ||||
-rw-r--r-- | keyutils.h | 3 | ||||
-rw-r--r-- | man/keyctl.1 | 92 | ||||
-rw-r--r-- | man/keyctl.3 | 2 | ||||
-rw-r--r-- | man/keyctl_watch_key.3 | 206 | ||||
-rw-r--r-- | tests/prepare.inc.sh | 1 | ||||
-rw-r--r-- | version.lds | 2 | ||||
-rw-r--r-- | watch_queue.h | 106 |
12 files changed, 970 insertions, 9 deletions
@@ -150,9 +150,11 @@ endif %.o: %.c keyutils.h Makefile $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $< -keyctl: keyctl.o keyctl_testing.o $(LIB_DEPENDENCY) - $(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ keyctl.o keyctl_testing.o -lkeyutils -keyctl.o keyctl_testing.o: keyctl.h +keyctl: keyctl.o keyctl_testing.o keyctl_watch.o $(LIB_DEPENDENCY) + $(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ \ + keyctl.o keyctl_testing.o keyctl_watch.o -lkeyutils +keyctl.o keyctl_testing.o keyctl_watch.o: keyctl.h +keyctl_watch.o: watch_queue.h request-key: request-key.o $(LIB_DEPENDENCY) $(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ $< -lkeyutils @@ -139,14 +139,16 @@ static const struct command commands[] = { { act_keyctl_timeout, "timeout", "<key> <timeout>" }, { act_keyctl_unlink, "unlink", "<key> [<keyring>]" }, { act_keyctl_update, "update", "[-x] <key> <data>" }, + { act_keyctl_watch, "watch", "<key>" }, + { act_keyctl_watch_add, "watch_add", "<fd> <key>" }, + { act_keyctl_watch_rm, "watch_rm", "<fd> <key>" }, + { act_keyctl_watch_session, "watch_session", "[-n <name>] <notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...]" }, + { act_keyctl_watch_sync, "watch_sync", "<fd>" }, { act_keyctl_test, "--test", "..." }, { NULL, NULL, NULL } }; static int dump_key_tree(key_serial_t keyring, const char *name, int hex_key_IDs); -static void format(void) __attribute__((noreturn)); -void error(const char *msg) __attribute__((noreturn)); -static key_serial_t get_key_id(char *arg); static void *read_file(const char *name, size_t *_size); static uid_t myuid; @@ -227,7 +229,7 @@ void do_command(int argc, char **argv, /* * display command format information */ -static void format(void) +void format(void) { const struct command *cmd; @@ -2288,6 +2290,7 @@ static const struct capability_def capabilities[] = { { "move_key", 0, KEYCTL_CAPS0_MOVE }, { "ns_keyring_name", 1, KEYCTL_CAPS1_NS_KEYRING_NAME }, { "ns_key_tag", 1, KEYCTL_CAPS1_NS_KEY_TAG }, + { "notify", 1, KEYCTL_CAPS1_NOTIFICATIONS }, {} }; @@ -2332,7 +2335,7 @@ static void act_keyctl_supports(int argc, char *argv[]) /* * parse a key identifier */ -static key_serial_t get_key_id(char *arg) +key_serial_t get_key_id(char *arg) { key_serial_t id; char *end; @@ -21,9 +21,20 @@ struct command { * keyctl.c */ extern nr void do_command(int, char **, const struct command *, const char *); -extern nr void error(const char *); +extern nr void format(void) __attribute__((noreturn)); +extern nr void error(const char *) __attribute__((noreturn)); +extern key_serial_t get_key_id(char *); /* * keyctl_testing.c */ extern nr void act_keyctl_test(int, char *[]); + +/* + * keyctl_watch.c + */ +extern nr void act_keyctl_watch(int , char *[]); +extern nr void act_keyctl_watch_add(int , char *[]); +extern nr void act_keyctl_watch_rm(int , char *[]); +extern nr void act_keyctl_watch_session(int , char *[]); +extern nr void act_keyctl_watch_sync(int , char *[]); diff --git a/keyctl_watch.c b/keyctl_watch.c new file mode 100644 index 0000000..ded64fa --- /dev/null +++ b/keyctl_watch.c @@ -0,0 +1,528 @@ +/* Key watching facility. + * + * 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. + */ + +#define _GNU_SOURCE +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> +#include <poll.h> +#include <getopt.h> +#include <sys/wait.h> +#include "keyutils.h" +#include <limits.h> +#include "keyctl.h" +#include "watch_queue.h" + +#define MAX_MESSAGE_COUNT 256 + +static int consumer_stop; +static pid_t pid_con = -1, pid_cmd = -1; +static key_serial_t session; +static int watch_fd; +static int debug; + +static inline bool after_eq(unsigned int a, unsigned int b) +{ + return (signed int)(a - b) >= 0; +} + +static void consumer_term(int sig) +{ + consumer_stop = 1; +} + +static void saw_key_change(FILE *log, struct watch_notification *n, + unsigned int len) +{ + struct key_notification *k = (struct key_notification *)n; + + if (len != sizeof(struct key_notification)) + return; + + switch (n->subtype) { + case NOTIFY_KEY_INSTANTIATED: + fprintf(log, "%u inst\n", k->key_id); + break; + case NOTIFY_KEY_UPDATED: + fprintf(log, "%u upd\n", k->key_id); + break; + case NOTIFY_KEY_LINKED: + fprintf(log, "%u link %u\n", k->key_id, k->aux); + break; + case NOTIFY_KEY_UNLINKED: + fprintf(log, "%u unlk %u\n", k->key_id, k->aux); + break; + case NOTIFY_KEY_CLEARED: + fprintf(log, "%u clr\n", k->key_id); + break; + case NOTIFY_KEY_REVOKED: + fprintf(log, "%u rev\n", k->key_id); + break; + case NOTIFY_KEY_INVALIDATED: + fprintf(log, "%u inv\n", k->key_id); + break; + case NOTIFY_KEY_SETATTR: + fprintf(log, "%u attr\n", k->key_id); + break; + } +} + +/* + * Handle removal notification. + */ +static void saw_removal_notification(FILE *gc, struct watch_notification *n, + unsigned int len) +{ + key_serial_t key = 0; + unsigned int wp; + + wp = (n->info & WATCH_INFO_ID) >> WATCH_INFO_ID__SHIFT; + + if (len >= sizeof(struct watch_notification_removal)) { + struct watch_notification_removal *r = (void *)n; + key = r->id; + } + + fprintf(gc, "%u gc\n", key); + if (wp == 1) + exit(0); +} + +/* + * Consume and display events. + */ +static __attribute__((noreturn)) +int consumer(FILE *log, FILE *gc, int fd) +{ + unsigned char buffer[433], *p, *end; + union { + struct watch_notification n; + unsigned char buf1[128]; + } n; + ssize_t buf_len; + + setlinebuf(log); + setlinebuf(gc); + signal(SIGTERM, consumer_term); + + do { + if (!consumer_stop) { + struct pollfd pf[1]; + pf[0].fd = fd; + pf[0].events = POLLIN; + pf[0].revents = 0; + + if (poll(pf, 1, -1) == -1) { + if (errno == EINTR) + continue; + error("poll"); + } + } + + buf_len = read(fd, buffer, sizeof(buffer)); + if (buf_len == -1) { + perror("read"); + exit(1); + } + + if (buf_len == 0) { + printf("-- END --\n"); + exit(0); + } + + if (buf_len > sizeof(buffer)) { + fprintf(stderr, "Read buffer overrun: %zd\n", buf_len); + exit(4); + } + + if (debug) + fprintf(stderr, "read() = %zd\n", buf_len); + + p = buffer; + end = buffer + buf_len; + while (p < end) { + size_t largest, len; + + largest = end - p; + if (largest > 128) + largest = 128; + if (largest < sizeof(struct watch_notification)) { + fprintf(stderr, "Short message header: %zu\n", largest); + exit(4); + } + memcpy(&n, p, largest); + + if (debug) + fprintf(stderr, "NOTIFY[%03zx]: ty=%06x sy=%02x i=%08x\n", + p - buffer, n.n.type, n.n.subtype, n.n.info); + + len = n.n.info & WATCH_INFO_LENGTH; + if (len < sizeof(n.n) || len > largest) { + fprintf(stderr, "Bad message length: %zu/%zu\n", len, largest); + exit(1); + } + + switch (n.n.type) { + case WATCH_TYPE_META: + switch (n.n.subtype) { + case WATCH_META_REMOVAL_NOTIFICATION: + saw_removal_notification(gc, &n.n, len); + break; + case WATCH_META_LOSS_NOTIFICATION: + fprintf(log, "-- LOSS --\n"); + break; + default: + if (debug) + fprintf(stderr, "other meta record\n"); + break; + } + break; + case WATCH_TYPE_KEY_NOTIFY: + saw_key_change(log, &n.n, len); + break; + default: + if (debug) + fprintf(stderr, "other type\n"); + break; + } + + p += len; + } + } while (!consumer_stop); + + fprintf(log, "Monitoring terminated\n"); + if (gc != log) + fprintf(gc, "Monitoring terminated\n"); + exit(0); +} + +static struct watch_notification_filter filter = { + .nr_filters = 1, + .__reserved = 0, + .filters = { + [0] = { + .type = WATCH_TYPE_KEY_NOTIFY, + .subtype_filter[0] = UINT_MAX, + }, + }, +}; + +/* + * Open the watch device and allocate a buffer. + */ +static int open_watch(void) +{ + int pipefd[2], fd; + + if (pipe2(pipefd, O_NOTIFICATION_PIPE | O_NONBLOCK) == -1) + error("pipe2"); + + fd = pipefd[0]; + + if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, MAX_MESSAGE_COUNT) == -1) + error("/dev/watch_queue(size)"); + + if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) + error("/dev/watch_queue(filter)"); + + return fd; +} + +/* + * Watch a key or keyring for changes. + */ +void act_keyctl_watch(int argc, char *argv[]) +{ + key_serial_t key; + int wfd; + + if (argc != 2) + format(); + + key = get_key_id(argv[1]); + wfd = open_watch(); + + if (keyctl_watch_key(key, wfd, 0x01) == -1) + error("keyctl_watch_key"); + + consumer(stdout, stdout, wfd); +} + +/* + * Add a watch on a key to the monitor created by watch_session. + */ +void act_keyctl_watch_add(int argc, char *argv[]) +{ + key_serial_t key; + int fd; + + if (argc != 3) + format(); + + fd = atoi(argv[1]); + key = get_key_id(argv[2]); + + if (keyctl_watch_key(key, fd, 0x02) == -1) + error("keyctl_watch_key"); + exit(0); +} + +/* + * Remove a watch on a key from the monitor created by watch_session. + */ +void act_keyctl_watch_rm(int argc, char *argv[]) +{ + key_serial_t key; + int fd; + + if (argc != 3) + format(); + + fd = atoi(argv[1]); + key = get_key_id(argv[2]); + + if (keyctl_watch_key(key, fd, -1) == -1) + error("keyctl_watch_key"); + exit(0); +} + +static void exit_cleanup(void) +{ + pid_t me = getpid(); + int w; + + if (me != pid_cmd && me != pid_con) { + keyctl_watch_key(session, watch_fd, -1); + if (pid_cmd != -1) { + kill(pid_cmd, SIGTERM); + waitpid(pid_cmd, &w, 0); + } + if (pid_con != -1) { + kill(pid_con, SIGTERM); + waitpid(pid_con, &w, 0); + } + } +} + +static void run_command(int argc, char *argv[], int wfd) +{ + char buf[16]; + + pid_cmd = fork(); + if (pid_cmd == -1) + error("fork"); + if (pid_cmd != 0) + return; + + pid_cmd = -1; + pid_con = -1; + + sprintf(buf, "%u", wfd); + setenv("KEYCTL_WATCH_FD", buf, true); + + /* run the standard shell if no arguments */ + if (argc == 0) { + const char *q = getenv("SHELL"); + if (!q) + q = "/bin/sh"; + execl(q, q, NULL); + error(q); + } + + /* run the command specified */ + execvp(argv[0], argv); + error(argv[0]); +} + +/* + * Open a logfiles. + */ +static FILE *open_logfile(const char *logfile) +{ + unsigned int flags; + FILE *log; + int lfd; + + log = fopen(logfile, "a"); + if (!log) + error(logfile); + + lfd = fileno(log); + flags = fcntl(lfd, F_GETFD); + if (flags == -1) + error("F_GETFD"); + if (fcntl(lfd, F_SETFD, flags | FD_CLOEXEC) == -1) + error("F_SETFD"); + + return log; +} + +/* + * Set up a new session keyring with a monitor that is exposed on an explicit + * file descriptor in the program that it starts. + */ +void act_keyctl_watch_session(int argc, char *argv[]) +{ + const char *session_name = NULL; + const char *logfile, *gcfile, *target_fd; + unsigned int flags; + pid_t pid; + FILE *log, *gc; + int wfd, tfd, opt, w, e = 0, e2 = 0; + + while (opt = getopt(argc, argv, "+dn:"), + opt != -1) { + switch (opt) { + case 'd': + debug = 1; + break; + case 'n': + session_name = optarg; + break; + default: + fprintf(stderr, "Unknown option\n"); + exit(2); + } + } + + argv += optind; + argc -= optind; + + if (argc < 4) + format(); + + logfile = argv[0]; + gcfile = argv[1]; + target_fd = argv[2]; + tfd = atoi(target_fd); + if (tfd < 3 || tfd > 9) { + fprintf(stderr, "The target fd must be between 3 and 9\n"); + exit(2); + } + + wfd = open_watch(); + if (wfd != tfd) { + if (dup2(wfd, tfd) == -1) + error("dup2"); + close(wfd); + wfd = tfd; + } + watch_fd = wfd; + + atexit(exit_cleanup); + + /* We want the fd to be inherited across a fork. */ + flags = fcntl(wfd, F_GETFD); + if (flags == -1) + error("F_GETFD"); + if (fcntl(wfd, F_SETFD, flags & ~FD_CLOEXEC) == -1) + error("F_SETFD"); + + log = open_logfile(logfile); + gc = open_logfile(gcfile); + + pid_con = fork(); + if (pid_con == -1) + error("fork"); + if (pid_con == 0) { + pid_cmd = -1; + pid_con = -1; + consumer(log, gc, wfd); + } + + /* Create a new session keyring and watch it. */ + session = keyctl_join_session_keyring(session_name); + if (session == -1) + error("keyctl_join_session_keyring"); + + if (keyctl_watch_key(session, wfd, 0x01) == -1) + error("keyctl_watch_key/session"); + + fprintf(stderr, "Joined session keyring: %d\n", session); + + /* Start the command and then wait for it to finish and the + * notification consumer to clean up. + */ + run_command(argc - 3, argv + 3, wfd); + close(wfd); + wfd = -1; + + while (pid = wait(&w), + pid != -1) { + if (pid == pid_cmd) { + if (pid_con != -1) + kill(pid_con, SIGTERM); + if (WIFEXITED(w)) { + e2 = WEXITSTATUS(w); + pid_cmd = -1; + } else if (WIFSIGNALED(w)) { + e2 = WTERMSIG(w) + 128; + pid_cmd = -1; + } else if (WIFSTOPPED(w)) { + raise(WSTOPSIG(w)); + } + } else if (pid == pid_con) { + if (pid_cmd != -1) + kill(pid_cmd, SIGTERM); + if (WIFEXITED(w)) { + e = WEXITSTATUS(w); + pid_con = -1; + } else if (WIFSIGNALED(w)) { + e = WTERMSIG(w) + 128; + pid_con = -1; + } + } + } + + if (e == 0) + e = e2; + exit(e); +} + +/* + * Wait for monitoring to synchronise. + */ +void act_keyctl_watch_sync(int argc, char *argv[]) +{ + long ret; + int wfd, count; + + if (argc != 2) + format(); + + wfd = atoi(argv[1]); + + ret = ioctl(wfd, PIPE_IOC_SYNC); + if (ret == 0) + exit(0); + + if (ret == -1 && errno != ENOTTY) + error("ioctl(PIPE_IOC_SYNC)"); + + for (;;) { + ret = ioctl(wfd, FIONREAD, &count); + if (ret == -1) + error("ioctl(FIONREAD)"); + if (count == 0) + break; + usleep(200 * 1000); + } + + exit(0); +} @@ -385,6 +385,11 @@ long keyctl_capabilities(unsigned char *buffer, size_t buflen) return sizeof(unsigned char); } +long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id) +{ + return keyctl(KEYCTL_WATCH_KEY, id, watch_queue_fd, watch_id); +} + /*****************************************************************************/ /* * fetch key description into an allocated buffer @@ -112,6 +112,7 @@ typedef uint32_t key_perm_t; #define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */ #define KEYCTL_MOVE 30 /* Move keys between keyrings */ #define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */ +#define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes */ /* keyctl structures */ struct keyctl_dh_params { @@ -168,6 +169,7 @@ struct keyctl_pkey_params { #define KEYCTL_CAPS0_MOVE 0x80 /* KEYCTL_MOVE supported */ #define KEYCTL_CAPS1_NS_KEYRING_NAME 0x01 /* Keyring names are per-user_namespace */ #define KEYCTL_CAPS1_NS_KEY_TAG 0x02 /* Key indexing can include a namespace tag */ +#define KEYCTL_CAPS1_NOTIFICATIONS 0x04 /* Keys generate watchable notifications */ /* * syscall wrappers @@ -255,6 +257,7 @@ extern long keyctl_move(key_serial_t id, key_serial_t to_ringid, unsigned int flags); extern long keyctl_capabilities(unsigned char *buffer, size_t buflen); +extern long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id); /* * utilities diff --git a/man/keyctl.1 b/man/keyctl.1 index 2f545bd..f18f92d 100644 --- a/man/keyctl.1 +++ b/man/keyctl.1 @@ -114,6 +114,15 @@ keyctl \- key management facility control \fBkeyctl\fR pkey_sign <key> <pass> <datafile> [k=v]* ><sigfile> .br \fBkeyctl\fR pkey_decrypt <key> <pass> <datafile> <sigfile> [k=v]* +.br +\fBkeyctl\fR watch <key> +.br +\fBkeyctl\fR watch_add <fd> <key> +.br +\fBkeyctl\fR watch_rm <fd> <key> +.br +\fBkeyctl\fR watch_session [-n <name>] \\ + <notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...] .SH DESCRIPTION This program is used to control the key management facility in various ways using a variety of subcommands. @@ -944,6 +953,89 @@ keyctl pkey_verify $k 0 foo.hash foo.sig enc=pkcs1 hash=sha256 .PP See asymmetric-key(7) for more information. +.SS Change notifications +\fBkeyctl\fR watch <key> +.br +\fBkeyctl\fR watch_session [-n <name>] \\ + <notifylog> <gclog> <fd> <prog> [<arg1> <arg2> ...] +\fBkeyctl\fR watch_add <fd> <key> +.br +\fBkeyctl\fR watch_rm <fd> <key> +.br +.PP +The +.B watch +command watches a single key, printing notifications to stdout until the key +is destroyed. +.PP +The output of the command looks like: +.PP +.RS +.nf +.RI < keyid "> <" event "> [<" aux ">]" +.fi +.RE +.PP +Where +.I keyid +is the primary subject of the notification, +.I op +is the event and +.I aux +is the secondary key if there is one (such as link where the primary key is +the keyring secondary key is the key being linked in to it). For example: +.PP +.RS +.nf +255913279 link 340681059 +255913279 clr +.fi +.RE +.PP +An additional notication is generated when a key being watched is garbage +collected, e.g.: +.PP +.RS +.nf +255913279 gc +.fi +.RE +.PP +The +.B watch_session +command creates a new session keyring, with name +.I name +if given, watches it for notifications and runs program +.I prog +with it. The program is given the specified arguments. +.PP +A second process is forked off to monitor the notifications. The output from +that is directed to the files +.I notifylog +for most notifications and +.I gclog +for key removal notifications (which are asynchronous and may be deferred). +.PP +The +.BR watch_queue (7) +device is exported to the program attached to fd number +.IR fd . +This can be passed by the other two commands. +.PP +The +.B watch_add +command adds a watch on +.I key +to the +.B watch_queue +attached to +.I fd +as exported by watch_session and the +.B watch_rm +caommand removes it. A watch_queue can handle multiple keys and even non-keys +sources as well. + + .SH ERRORS There are a number of common errors returned by this program: diff --git a/man/keyctl.3 b/man/keyctl.3 index cb0449c..fda0363 100644 --- a/man/keyctl.3 +++ b/man/keyctl.3 @@ -104,6 +104,8 @@ and then telling the linker it should link in the library: .BR keyctl_unlink (3) .br .BR keyctl_update (3) +.br +.BR keyctl_watch_key (3) .\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .SH UTILITY FUNCTIONS .BR find_key_by_type_and_name (3) diff --git a/man/keyctl_watch_key.3 b/man/keyctl_watch_key.3 new file mode 100644 index 0000000..044b7a3 --- /dev/null +++ b/man/keyctl_watch_key.3 @@ -0,0 +1,206 @@ +.\" +.\" 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 License +.\" as published by the Free Software Foundation; either version +.\" 2 of the License, or (at your option) any later version. +.\" +.TH KEYCTL_GRANT_PERMISSION 3 "28 Aug 2019" Linux "Linux Key Management Calls" +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH NAME +keyctl_watch_key \- Watch for changes to a key +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH SYNOPSIS +.nf +.B #include <keyutils.h> +.sp +.BI "long keyctl_watch_key(key_serial_t " key , +.BI " int " watch_queue_fd +.BI " int " watch_id ");" +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH DESCRIPTION +.BR keyctl_watch_key () +sets or removes a watch on +.IR key . +.PP +.I watch_id +specifies the ID for a watch that will be included in notification messages. +It can be between 0 and 255 to add a key; it should be -1 to remove a key. +.PP +.I watch_queue_fd +is a file descriptor attached to a watch_queue device instance. Multiple +openings of a device provide separate instances. Each device instance can +only have one watch on any particular key. +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SS Notification Record +.PP +Key-specific notification messages that the kernel emits into the buffer have +the following format: +.PP +.in +4n +.EX +struct key_notification { + struct watch_notification watch; + __u32 key_id; + __u32 aux; +}; +.EE +.in +.PP +The +.I watch.type +field will be set to +.B WATCH_TYPE_KEY_NOTIFY +and the +.I watch.subtype +field will contain one of the following constants, indicating the event that +occurred and the watch_id passed to keyctl_watch_key() will be placed in +.I watch.info +in the ID field. The following events are defined: +.TP +.B NOTIFY_KEY_INSTANTIATED +This indicates that a watched key got instantiated or negatively instantiated. +.I key_id +indicates the key that was instantiated and +.I aux +is unused. +.TP +.B NOTIFY_KEY_UPDATED +This indicates that a watched key got updated or instantiated by update. +.I key_id +indicates the key that was updated and +.I aux +is unused. +.TP +.B NOTIFY_KEY_LINKED +This indicates that a key got linked into a watched keyring. +.I key_id +indicates the keyring that was modified +.I aux +indicates the key that was added. +.TP +.B NOTIFY_KEY_UNLINKED +This indicates that a key got unlinked from a watched keyring. +.I key_id +indicates the keyring that was modified +.I aux +indicates the key that was removed. +.TP +.B NOTIFY_KEY_CLEARED +This indicates that a watched keyring got cleared. +.I key_id +indicates the keyring that was cleared and +.I aux +is unused. +.TP +.B NOTIFY_KEY_REVOKED +This indicates that a watched key got revoked. +.I key_id +indicates the key that was revoked and +.I aux +is unused. +.TP +.B NOTIFY_KEY_INVALIDATED +This indicates that a watched key got invalidated. +.I key_id +indicates the key that was invalidated and +.I aux +is unused. +.TP +.B NOTIFY_KEY_SETATTR +This indicates that a watched key had its attributes (owner, group, +permissions, timeout) modified. +.I key_id +indicates the key that was modified and +.I aux +is unused. +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SS Removal Notification +When a watched key is garbage collected, all of its watches are automatically +destroyed and a notification is delivered to each watcher. This will normally +be an extended notification of the form: +.PP +.in +4n +.EX +struct watch_notification_removal { + struct watch_notification watch; + __u64 id; +}; +.EE +.in +.PP +The +.I watch.type +field will be set to +.B WATCH_TYPE_META +and the +.I watch.subtype +field will contain +.BR WATCH_META_REMOVAL_NOTIFICATION . +If the extended notification is given, then the length will be 2 units, +otherwise it will be 1 and only the header will be present. +.PP +The watch_id passed to +.IR keyctl_watch_key () +will be placed in +.I watch.info +in the ID field. +.PP +If the extension is present, +.I id +will be set to the ID of the destroyed key. +.PP +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH RETURN VALUE +On success +.BR keyctl_watch_key () +returns +.B 0 . +On error, the value +.B -1 +will be returned and +.I errno +will have been set to an appropriate error. +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH ERRORS +.TP +.B ENOKEY +The specified key does not exist. +.TP +.B EKEYEXPIRED +The specified key has expired. +.TP +.B EKEYREVOKED +The specified key has been revoked. +.TP +.B EACCES +The named key exists, but does not grant +.B view +permission to the calling process. +.TP +.B EBUSY +The specified key already has a watch on it for that device instance (add +only). +.TP +.B EBADSLT +The specified key doesn't have a watch on it (removal only). +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH LINKING +This is a library function that can be found in +.IR libkeyutils . +When linking, +.B \-lkeyutils +should be specified to the linker. +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.SH SEE ALSO +.ad l +.nh +.BR keyctl (1), +.BR add_key (2), +.BR keyctl (2), +.BR request_key (2), +.BR keyctl (3), +.BR keyrings (7), +.BR keyutils (7) diff --git a/tests/prepare.inc.sh b/tests/prepare.inc.sh index 448e42b..a306ed0 100644 --- a/tests/prepare.inc.sh +++ b/tests/prepare.inc.sh @@ -74,6 +74,7 @@ have_key_invalidate=0 have_big_key_type=0 have_dh_compute=0 have_restrict_keyring=0 +have_notify=0 if keyctl supports capabilities >&/dev/null then diff --git a/version.lds b/version.lds index 2a6e142..6c34adf 100644 --- a/version.lds +++ b/version.lds @@ -100,5 +100,7 @@ KEYUTILS_1.9 { } KEYUTILS_1.8; KEYUTILS_1.10 { + /* Management functions */ + keyctl_watch_key; } KEYUTILS_1.9; diff --git a/watch_queue.h b/watch_queue.h new file mode 100644 index 0000000..68f103b --- /dev/null +++ b/watch_queue.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _KEYUTILS_LINUX_WATCH_QUEUE_H +#define _KEYUTILS_LINUX_WATCH_QUEUE_H + +#include <linux/types.h> +#include <sys/ioctl.h> + +#define O_NOTIFICATION_PIPE __O_TMPFILE /* Parameter to pipe2() */ + +#define IOC_WATCH_QUEUE_SET_SIZE _IO('W', 0x60) /* Set the size in pages */ +#define IOC_WATCH_QUEUE_SET_FILTER _IO('W', 0x61) /* Set the filter */ +#define PIPE_IOC_SYNC _IO('W', 0x62) /* Wait for data in pipe to be read */ + +enum watch_notification_type { + WATCH_TYPE_META = 0, /* Special record */ + WATCH_TYPE_KEY_NOTIFY = 1, /* Key change event notification */ + WATCH_TYPE_BLOCK_NOTIFY = 2, /* Block layer event notification */ + WATCH_TYPE_USB_NOTIFY = 3, /* USB subsystem event notification */ + WATCH_TYPE___NR = 4 +}; + +enum watch_meta_notification_subtype { + WATCH_META_REMOVAL_NOTIFICATION = 0, /* Watched object was removed */ + WATCH_META_LOSS_NOTIFICATION = 1, /* Data loss occurred */ +}; + +/* + * Notification record header. This is aligned to 64-bits so that subclasses + * can contain __u64 fields. + */ +struct watch_notification { + __u32 type:24; /* enum watch_notification_type */ + __u32 subtype:8; /* Type-specific subtype (filterable) */ + __u32 info; +#define WATCH_INFO_LENGTH 0x0000007f /* Length of record / sizeof(watch_notification) */ +#define WATCH_INFO_LENGTH__SHIFT 0 +#define WATCH_INFO_ID 0x0000ff00 /* ID of watchpoint, if type-appropriate */ +#define WATCH_INFO_ID__SHIFT 8 +#define WATCH_INFO_TYPE_INFO 0xffff0000 /* Type-specific info */ +#define WATCH_INFO_TYPE_INFO__SHIFT 16 +#define WATCH_INFO_FLAG_0 0x00010000 /* Type-specific info, flag bit 0 */ +#define WATCH_INFO_FLAG_1 0x00020000 /* ... */ +#define WATCH_INFO_FLAG_2 0x00040000 +#define WATCH_INFO_FLAG_3 0x00080000 +#define WATCH_INFO_FLAG_4 0x00100000 +#define WATCH_INFO_FLAG_5 0x00200000 +#define WATCH_INFO_FLAG_6 0x00400000 +#define WATCH_INFO_FLAG_7 0x00800000 +}; + +/* + * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER). + */ +struct watch_notification_type_filter { + __u32 type; /* Type to apply filter to */ + __u32 info_filter; /* Filter on watch_notification::info */ + __u32 info_mask; /* Mask of relevant bits in info_filter */ + __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */ +}; + +struct watch_notification_filter { + __u32 nr_filters; /* Number of filters */ + __u32 __reserved; /* Must be 0 */ + struct watch_notification_type_filter filters[]; +}; + + +/* + * Extended watch removal notification. This is used optionally if the type + * wants to indicate an identifier for the object being watched, if there is + * such. This can be distinguished by the length. + * + * type -> WATCH_TYPE_META + * subtype -> WATCH_META_REMOVAL_NOTIFICATION + */ +struct watch_notification_removal { + struct watch_notification watch; + __u64 id; /* Type-dependent identifier */ +}; + +/* + * Type of key/keyring change notification. + */ +enum key_notification_subtype { + NOTIFY_KEY_INSTANTIATED = 0, /* Key was instantiated (aux is error code) */ + NOTIFY_KEY_UPDATED = 1, /* Key was updated */ + NOTIFY_KEY_LINKED = 2, /* Key (aux) was added to watched keyring */ + NOTIFY_KEY_UNLINKED = 3, /* Key (aux) was removed from watched keyring */ + NOTIFY_KEY_CLEARED = 4, /* Keyring was cleared */ + NOTIFY_KEY_REVOKED = 5, /* Key was revoked */ + NOTIFY_KEY_INVALIDATED = 6, /* Key was invalidated */ + NOTIFY_KEY_SETATTR = 7, /* Key's attributes got changed */ +}; + +/* + * Key/keyring notification record. + * - watch.type = WATCH_TYPE_KEY_NOTIFY + * - watch.subtype = enum key_notification_type + */ +struct key_notification { + struct watch_notification watch; + __u32 key_id; /* The key/keyring affected */ + __u32 aux; /* Per-type auxiliary data */ +}; + +#endif /* _KEYUTILS_LINUX_WATCH_QUEUE_H */ |