[RFC,V2] selinux-testsuite: Add check for key changes on watch_queue
diff mbox series

Message ID 20200515131222.7969-1-richard_c_haines@btinternet.com
State Accepted
Delegated to: Ondrej Mosnáček
Headers show
Series
  • [RFC,V2] selinux-testsuite: Add check for key changes on watch_queue
Related show

Commit Message

Richard Haines May 15, 2020, 1:12 p.m. UTC
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

Comments

Ondrej Mosnacek June 15, 2020, 12:08 p.m. UTC | #1
On Fri, May 15, 2020 at 3:12 PM Richard Haines
<richard_c_haines@btinternet.com> wrote:
> Kernel 5.? introduced the watch_queue service that allows watching for

(I will fix up 5.? to 5.8 when applying.)

> 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'

I tested the patch on the latest Rawhide 5.8-rc1 kernel build and it
seems to work fine.

Acked-by: Ondrej Mosnacek <omosnace@redhat.com>

> 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
[...]
> diff --git a/tests/watchkey/watchkey.c b/tests/watchkey/watchkey.c
> new file mode 100644
> index 0000000..c7f3274
> --- /dev/null
> +++ b/tests/watchkey/watchkey.c
[...]
> +       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);

Minor nit: we should actually close both pipefds here (the write end
(pipefd[1]) is unused, but according to strace, the kernel does return
a valid fd there, too). Also, there is one error path that just calls
exit(-1) instead of closing the descriptors. Anyway, since this is
just a test program and the kernel closes the fds at exit anyway, I'm
not going to hold up the patch because of it. Feel free to send a
separate patch if you'd like to clean it up.

> +       return save_errno;
> +}
> --
> 2.25.3
>


--
Ondrej Mosnacek
Software Engineer, Platform Security - SELinux kernel
Red Hat, Inc.
Ondrej Mosnacek June 16, 2020, 9 a.m. UTC | #2
On Mon, Jun 15, 2020 at 2:08 PM Ondrej Mosnacek <omosnace@redhat.com> wrote:
> On Fri, May 15, 2020 at 3:12 PM Richard Haines
> <richard_c_haines@btinternet.com> wrote:
> > Kernel 5.? introduced the watch_queue service that allows watching for
>
> (I will fix up 5.? to 5.8 when applying.)
>
> > 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'
>
> I tested the patch on the latest Rawhide 5.8-rc1 kernel build and it
> seems to work fine.
>
> Acked-by: Ondrej Mosnacek <omosnace@redhat.com>

Now applied, thanks!

Patch
diff mbox series

diff --git a/defconfig b/defconfig
index 00bf9f3..68155dd 100644
--- a/defconfig
+++ b/defconfig
@@ -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
diff --git a/policy/Makefile b/policy/Makefile
index 386bbce..672733e 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -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)
diff --git a/policy/test_watchkey.te b/policy/test_watchkey.te
new file mode 100644
index 0000000..00ade15
--- /dev/null
+++ b/policy/test_watchkey.te
@@ -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)
diff --git a/tests/Makefile b/tests/Makefile
index 5b86a2b..bdbdf3e 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -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
diff --git a/tests/watchkey/.gitignore b/tests/watchkey/.gitignore
new file mode 100644
index 0000000..04db718
--- /dev/null
+++ b/tests/watchkey/.gitignore
@@ -0,0 +1 @@ 
+watchkey
diff --git a/tests/watchkey/Makefile b/tests/watchkey/Makefile
new file mode 100644
index 0000000..c2796fb
--- /dev/null
+++ b/tests/watchkey/Makefile
@@ -0,0 +1,7 @@ 
+TARGETS = watchkey
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/watchkey/test b/tests/watchkey/test
new file mode 100755
index 0000000..f61ff78
--- /dev/null
+++ b/tests/watchkey/test
@@ -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;
diff --git a/tests/watchkey/watchkey.c b/tests/watchkey/watchkey.c
new file mode 100644
index 0000000..c7f3274
--- /dev/null
+++ b/tests/watchkey/watchkey.c
@@ -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;
+}