From 5ac409b01cc4fcb69d903408da90df2d34d4f32a Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 3 Sep 2019 11:04:42 +0100 Subject: Add the ability to supply filters to watches set with keyctl Add the ability to supply filters to watches set with "keyctl watch" and "keyctl watch_session". Signed-off-by: David Howells --- keyctl.c | 4 +- keyctl_watch.c | 101 +++++++++++++++++++++++++++------ man/keyctl.1 | 27 +++++++-- tests/keyctl/watch/bad-args/runtest.sh | 39 +++++++++++++ tests/keyctl/watch/noargs/runtest.sh | 27 +++++++++ tests/toolbox.inc.sh | 94 ++++++++++++++++++++++++++++++ 6 files changed, 269 insertions(+), 23 deletions(-) create mode 100644 tests/keyctl/watch/bad-args/runtest.sh create mode 100644 tests/keyctl/watch/noargs/runtest.sh diff --git a/keyctl.c b/keyctl.c index cc25ac0..b1e100e 100644 --- a/keyctl.c +++ b/keyctl.c @@ -139,10 +139,10 @@ static const struct command commands[] = { { act_keyctl_timeout, "timeout", " " }, { act_keyctl_unlink, "unlink", " []" }, { act_keyctl_update, "update", "[-x] " }, - { act_keyctl_watch, "watch", "" }, + { act_keyctl_watch, "watch", "[-f] " }, { act_keyctl_watch_add, "watch_add", " " }, { act_keyctl_watch_rm, "watch_rm", " " }, - { act_keyctl_watch_session, "watch_session", "[-n ] [ ...]" }, + { act_keyctl_watch_session, "watch_session", "[-f] [-n ] [ ...]" }, { act_keyctl_watch_sync, "watch_sync", "" }, { act_keyctl_test, "--test", "..." }, { NULL, NULL, NULL } diff --git a/keyctl_watch.c b/keyctl_watch.c index 191fe51..5415d4c 100644 --- a/keyctl_watch.c +++ b/keyctl_watch.c @@ -38,6 +38,16 @@ static key_serial_t session; static int watch_fd; static int debug; +static struct watch_notification_filter filter = { + .nr_filters = 0, + .filters = { + /* Reserve a slot */ + [0] = { + .type = WATCH_TYPE_KEY_NOTIFY, + }, + }, +}; + static inline bool after_eq(unsigned int a, unsigned int b) { return (signed int)(a - b) >= 0; @@ -172,17 +182,6 @@ int consumer(FILE *log, FILE *gc, int fd, struct watch_queue_buffer *buf) 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. */ @@ -199,7 +198,8 @@ static int open_watch(struct watch_queue_buffer **_buf) if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) error("/dev/watch_queue(size)"); - if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) + if (filter.nr_filters && + ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) error("/dev/watch_queue(filter)"); page_size = sysconf(_SC_PAGESIZE); @@ -212,6 +212,58 @@ static int open_watch(struct watch_queue_buffer **_buf) return fd; } +/* + * Parse a filter character representation into a subtype number. + */ +static bool parse_subtype(struct watch_notification_type_filter *t, char filter) +{ + static const char filter_mapping[] = + "i" /* 0 NOTIFY_KEY_INSTANTIATED */ + "p" /* 1 NOTIFY_KEY_UPDATED */ + "l" /* 2 NOTIFY_KEY_LINKED */ + "n" /* 3 NOTIFY_KEY_UNLINKED */ + "c" /* 4 NOTIFY_KEY_CLEARED */ + "r" /* 5 NOTIFY_KEY_REVOKED */ + "v" /* 6 NOTIFY_KEY_INVALIDATED */ + "s" /* 7 NOTIFY_KEY_SETATTR */ + ; + const char *p; + unsigned int st_bits; + unsigned int st_index; + unsigned int st_bit; + int subtype; + + p = strchr(filter_mapping, filter); + if (!p) + return false; + + subtype = p - filter_mapping; + st_bits = sizeof(t->subtype_filter[0]) * 8; + st_index = subtype / st_bits; + st_bit = 1U << (subtype % st_bits); + t->subtype_filter[st_index] |= st_bit; + return true; +} + +/* + * Parse filters. + */ +static void parse_watch_filter(char *str) +{ + struct watch_notification_filter *f = &filter; + struct watch_notification_type_filter *t0 = &f->filters[0]; + + f->nr_filters = 1; + t0->type = WATCH_TYPE_KEY_NOTIFY; + + for (; *str; str++) { + if (parse_subtype(t0, *str)) + continue; + fprintf(stderr, "Unknown filter character '%c'\n", *str); + exit(2); + } +} + /* * Watch a key or keyring for changes. */ @@ -219,12 +271,26 @@ void act_keyctl_watch(int argc, char *argv[]) { struct watch_queue_buffer *buf; key_serial_t key; - int wfd; + int wfd, opt; - if (argc != 2) + while (opt = getopt(argc, argv, "f:"), + opt != -1) { + switch (opt) { + case 'f': + parse_watch_filter(optarg); + break; + default: + fprintf(stderr, "Unknown option\n"); + exit(2); + } + } + + argv += optind; + argc -= optind; + if (argc != 1) format(); - key = get_key_id(argv[1]); + key = get_key_id(argv[0]); wfd = open_watch(&buf); if (keyctl_watch_key(key, wfd, 0x01) == -1) @@ -356,12 +422,15 @@ void act_keyctl_watch_session(int argc, char *argv[]) FILE *log, *gc; int wfd, tfd, opt, w, e = 0, e2 = 0; - while (opt = getopt(argc, argv, "+dn:"), + while (opt = getopt(argc, argv, "+df:n:"), opt != -1) { switch (opt) { case 'd': debug = 1; break; + case 'f': + parse_watch_filter(optarg); + break; case 'n': session_name = optarg; break; diff --git a/man/keyctl.1 b/man/keyctl.1 index f18f92d..2343762 100644 --- a/man/keyctl.1 +++ b/man/keyctl.1 @@ -115,13 +115,13 @@ keyctl \- key management facility control .br \fBkeyctl\fR pkey_decrypt [k=v]* .br -\fBkeyctl\fR watch +\fBkeyctl\fR watch [\-f] .br \fBkeyctl\fR watch_add .br \fBkeyctl\fR watch_rm .br -\fBkeyctl\fR watch_session [-n ] \\ +\fBkeyctl\fR watch_session [\-f ] [-n ] \\ [ ...] .SH DESCRIPTION This program is used to control the key management facility in various ways @@ -954,9 +954,9 @@ keyctl pkey_verify $k 0 foo.hash foo.sig enc=pkcs1 hash=sha256 See asymmetric-key(7) for more information. .SS Change notifications -\fBkeyctl\fR watch +\fBkeyctl\fR watch [\-f] .br -\fBkeyctl\fR watch_session [-n ] \\ +\fBkeyctl\fR watch_session [\-f ] [-n ] \\ [ ...] \fBkeyctl\fR watch_add .br @@ -966,7 +966,24 @@ See asymmetric-key(7) for more information. The .B watch command watches a single key, printing notifications to stdout until the key -is destroyed. +is destroyed. Filters can be employed to cut down the events that will be +delivered. The +.I filter +string is a series of letters, each one of which enables a particular event +subtype: +.PP +.RS +.nf +.BR i " - The key has been instantiated" +.BR p " - The key has been updated" +.BR l " - A link has been added to a keyring" +.BR n " - A link has been removed from a keyring" +.BR c " - A keyring has been cleared" +.BR r " - A key has been revoked" +.BR v " - A key has been invalidated" +.BR s " - A key has had its attributes changed" +.fi +.RE .PP The output of the command looks like: .PP diff --git a/tests/keyctl/watch/bad-args/runtest.sh b/tests/keyctl/watch/bad-args/runtest.sh new file mode 100644 index 0000000..8bbd8c0 --- /dev/null +++ b/tests/keyctl/watch/bad-args/runtest.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +. ../../../prepare.inc.sh +. ../../../toolbox.inc.sh + + +# ---- do the actual testing ---- + +result=PASS +echo "++++ BEGINNING TEST" >$OUTPUTFILE + +# Attempt to watch an invalid key +marker "CHECK WATCH INVALID KEY" +watch_key --fail 0 +expect_error EINVAL + +# Add a user key to the session keyring for us to play with +marker "ADD USER KEY" +create_key --new=keyid user wibble stuff @s + +# Remove the key we just added +marker "UNLINK KEY" +unlink_key --wait $keyid @s + +# It should fail when we attempt to watch it +marker "UPDATE UNLINKED KEY" +watch_key --fail $keyid +expect_error ENOKEY + +# Try a number of dodgy filters +marker "CHECK DODGY FILTERS" +watch_key --fail2 -fZ @s +watch_key --fail2 -fZ -fQ @s +watch_key --fail2 -f: @s + +echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE + +# --- then report the results in the database --- +toolbox_report_result $TEST $result diff --git a/tests/keyctl/watch/noargs/runtest.sh b/tests/keyctl/watch/noargs/runtest.sh new file mode 100644 index 0000000..e2bc7d7 --- /dev/null +++ b/tests/keyctl/watch/noargs/runtest.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +. ../../../prepare.inc.sh +. ../../../toolbox.inc.sh + + +# ---- do the actual testing ---- + +result=PASS +echo "++++ BEGINNING TEST" >$OUTPUTFILE + +# check that no arguments fails correctly +marker "WATCH NO ARGS" +expect_args_error keyctl watch + +# check that two arguments fail correctly +marker "WATCH TWO ARGS" +expect_args_error keyctl watch @s @s + +# Try a dodgy filter +marker "CHECK BAD FILTER" +expect_args_error keyctl watch_key -f 0 + +echo "++++ FINISHED TEST: $result" >>$OUTPUTFILE + +# --- then report the results in the database --- +toolbox_report_result $TEST $result diff --git a/tests/toolbox.inc.sh b/tests/toolbox.inc.sh index 81d639c..609a6c7 100644 --- a/tests/toolbox.inc.sh +++ b/tests/toolbox.inc.sh @@ -1832,6 +1832,100 @@ function set_gc_delay() fi } +############################################################################### +# +# watch a key +# +############################################################################### +function watch_key () +{ + my_exitval=0 + if [ "x$1" = "x--fail" ] + then + my_exitval=1 + shift + elif [ "x$1" = "x--fail2" ] + then + my_exitval=2 + shift + fi + + echo keyctl watch "$@" >>$OUTPUTFILE + nice --adjustment=-3 keyctl watch "$@" >>$PWD/notify.log 2>>$OUTPUTFILE + if [ $? != $my_exitval ] + then + failed + fi +} + +############################################################################### +# +# Check for a notification +# +# expect_notification [--filter=[i|p|l|n|c|r|v|s]] [] +# +############################################################################### +function expect_notification () +{ + local want + + local filter="" + case "x$1" in + x--filter*) + case $1 in + --filter=) filter=;; + --filter=i) filter=inst;; + --filter=p) filter=upd;; + --filter=l) filter=link;; + --filter=n) filter=unlk;; + --filter=c) filter=clr;; + --filter=r) filter=rev;; + --filter=v) filter=inv;; + --filter=s) filter=attr;; + *) + echo "Unknown param $1 to expect_notification()" >&2 + exit 2 + ;; + esac + shift + ;; + esac + + if [ $# = 2 ] + then + want="$1 $2" + op=$2 + elif [ $# = 3 ] + then + want="$1 $2 $3" + op=$2 + else + echo "Wrong parameters to expect_notification" >&2 + exit 2 + fi + + if tail -3 $PWD/notify.log | grep "^${want}\$" >/dev/null + then + echo "Found notification '$*'" >>$OUTPUTFILE + if [ "$filter" != "" -a $op != "$filter" ] + then + echo "Notification '$want' should be filtered" >&2 + failed + fi + else + echo "Notification '$*' not present" >>$OUTPUTFILE + if [ "$filter" = "" ] + then + echo "Missing notification '$want'" >&2 + failed + elif [ $op = "$filter" ] + then + echo "Notification unexpectedly filtered '$want' $filter" >&2 + failed + fi + fi +} + ############################################################################### # # Note the creation of a new key -- cgit v1.2.1