@@ -110,3 +110,8 @@ CONFIG_NFSD_V4_SECURITY_LABEL=y
CONFIG_XFS_FS=m
CONFIG_XFS_QUOTA=y
CONFIG_VFAT_FS=m
+
+# watch_queue for key changes.
+# They are not required for SELinux operation itself.
+CONFIG_WATCH_QUEUE=y
+CONFIG_KEY_NOTIFICATIONS=y
@@ -104,7 +104,7 @@ TARGETS += test_bpf.te test_fdreceive_bpf.te test_binder_bpf.te
endif
ifeq ($(shell grep -q all_key_perms $(POLDEV)/include/support/all_perms.spt && echo true),true)
-TARGETS += test_keys.te
+TARGETS += test_keys.te test_watchkey.te
endif
ifeq ($(shell grep -q all_file_perms.*watch $(POLDEV)/include/support/all_perms.spt && echo true),true)
new file mode 100644
@@ -0,0 +1,27 @@
+#
+######### Check watch_queue for key changes policy module ##########
+#
+attribute watchkeydomain;
+
+################# Allow watch_queue key { view } ##########################
+type test_watchkey_t;
+# Note: allow rules for pipe2(2) 'fifo_file { ioctl }' are set via domain_type()
+domain_type(test_watchkey_t)
+unconfined_runs_test(test_watchkey_t)
+typeattribute test_watchkey_t testdomain;
+typeattribute test_watchkey_t watchkeydomain;
+
+allow test_watchkey_t self:key { view };
+
+################# Deny watch_queue key { view } ##########################
+type test_watchkey_no_view_t;
+domain_type(test_watchkey_no_view_t)
+unconfined_runs_test(test_watchkey_no_view_t)
+typeattribute test_watchkey_no_view_t testdomain;
+typeattribute test_watchkey_no_view_t watchkeydomain;
+
+#
+########### Allow these domains to be entered from sysadm domain ############
+#
+miscfiles_domain_entry_test_files(watchkeydomain)
+userdom_sysadm_entry_spec_domtrans_to(watchkeydomain)
@@ -119,6 +119,10 @@ SUBDIRS += fs_filesystem
endif
endif
+ifeq ($(shell grep -q all_key_perms $(POLDEV)/include/support/all_perms.spt && test -e $(INCLUDEDIR)/linux/watch_queue.h && grep -qs KEYCTL_WATCH_KEY $(INCLUDEDIR)/linux/keyctl.h && echo true),true)
+SUBDIRS += watchkey
+endif
+
ifeq ($(DISTRO),RHEL4)
SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
endif
new file mode 100644
@@ -0,0 +1 @@
+watchkey
new file mode 100644
@@ -0,0 +1,7 @@
+TARGETS = watchkey
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+
+clean:
+ rm -f $(TARGETS)
new file mode 100755
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+use Test::More;
+
+BEGIN {
+ $basedir = $0;
+ $basedir =~ s|(.*)/[^/]*|$1|;
+
+ # allow info to be shown during tests
+ $v = $ARGV[0];
+ if ($v) {
+ if ( $v ne "-v" ) {
+ plan skip_all => "Invalid option (use -v)";
+ }
+ }
+ else {
+ $v = " ";
+ }
+
+ plan tests => 2;
+}
+
+$result = system "runcon -t test_watchkey_t $basedir/watchkey $v";
+ok( $result eq 0 );
+
+# Deny key { view } - EACCES
+$result = system "runcon -t test_watchkey_no_view_t $basedir/watchkey $v 2>&1";
+ok( $result >> 8 eq 13 );
+
+exit;
new file mode 100644
@@ -0,0 +1,94 @@
+/* Based on kernel source samples/watch_queue/watch_test.c */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/watch_queue.h>
+#include <linux/unistd.h>
+#include <linux/keyctl.h>
+#include <selinux/selinux.h>
+
+#define BUF_SIZE 256
+
+/* Require syscall() as function not yet in libkeyutils */
+static long keyctl_watch_key(int key, int watch_fd, int watch_id)
+{
+ return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id);
+}
+
+static void print_usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [-v]\n"
+ "Where:\n\t"
+ "-v Print information.\n", progname);
+ exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+ int opt, fd, pipefd[2], result, save_errno;
+ char *context;
+ bool verbose = false;
+
+ while ((opt = getopt(argc, argv, "v")) != -1) {
+ switch (opt) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ print_usage(argv[0]);
+ }
+ }
+
+ if (verbose) {
+ result = getcon(&context);
+ if (result < 0) {
+ fprintf(stderr, "Failed to obtain process context\n");
+ exit(-1);
+ }
+
+ printf("Process context:\n\t%s\n", context);
+ free(context);
+ }
+
+ result = pipe2(pipefd, O_NOTIFICATION_PIPE);
+ if (result < 0) {
+ fprintf(stderr, "Failed to create pipe2(2): %s\n",
+ strerror(errno));
+ exit(-1);
+ }
+ fd = pipefd[0];
+
+ result = ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
+ if (result < 0) {
+ fprintf(stderr, "Failed to set watch_queue size: %s\n",
+ strerror(errno));
+ exit(-1);
+ }
+
+ save_errno = 0;
+ /* Requires key { view } */
+ result = keyctl_watch_key(KEY_SPEC_PROCESS_KEYRING, fd,
+ WATCH_TYPE_KEY_NOTIFY);
+ if (result < 0) {
+ save_errno = errno;
+ fprintf(stderr, "Failed keyctl_watch_key(): %s\n",
+ strerror(errno));
+ goto err;
+ }
+ if (verbose)
+ printf("keyctl_watch_key() successful\n");
+
+err:
+ close(fd);
+ return save_errno;
+}
Kernel 5.? introduced the watch_queue service that allows watching for key changes. This requires key { view } permission, therefore check if allowed or not. Note that the keyctl_watch_key() function is not yet built into the keyutils library, therefore a syscall() is used. Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> --- Tested on kernel.org 'linux-next: next-20200514' V2 Changes: 1) The watch_queue changed from using /dev/watch_queue to pipe2(2), therefore update watchkey.c 2) Update policy to reflect the changes. defconfig | 5 +++ policy/Makefile | 2 +- policy/test_watchkey.te | 27 +++++++++++ tests/Makefile | 4 ++ tests/watchkey/.gitignore | 1 + tests/watchkey/Makefile | 7 +++ tests/watchkey/test | 29 ++++++++++++ tests/watchkey/watchkey.c | 94 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 policy/test_watchkey.te create mode 100644 tests/watchkey/.gitignore create mode 100644 tests/watchkey/Makefile create mode 100755 tests/watchkey/test create mode 100644 tests/watchkey/watchkey.c