diff mbox series

selinux-testsuite: Add key and key_socket tests

Message ID 20190909131701.29588-1-richard_c_haines@btinternet.com (mailing list archive)
State Changes Requested
Headers show
Series selinux-testsuite: Add key and key_socket tests | expand

Commit Message

Richard Haines Sept. 9, 2019, 1:17 p.m. UTC
Test all permissions associated with the key and key_socket classes.

Note that kernel 5.3 commit keys: Fix request_key() lack of Link perm
check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
added an additional check for link perm on request_key(). The tests
will support earlier kernels.

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
---
 README.md                   |   3 +-
 defconfig                   |   8 ++
 policy/Makefile             |   4 +
 policy/test_keys.te         | 161 ++++++++++++++++++++++++
 tests/Makefile              |   4 +
 tests/keys/.gitignore       |   3 +
 tests/keys/Makefile         |   8 ++
 tests/keys/key_sock.c       |  67 ++++++++++
 tests/keys/keyctl.c         | 241 ++++++++++++++++++++++++++++++++++++
 tests/keys/keyctl_relabel.c |  93 ++++++++++++++
 tests/keys/test             |  98 +++++++++++++++
 11 files changed, 689 insertions(+), 1 deletion(-)
 create mode 100644 policy/test_keys.te
 create mode 100644 tests/keys/.gitignore
 create mode 100644 tests/keys/Makefile
 create mode 100644 tests/keys/key_sock.c
 create mode 100644 tests/keys/keyctl.c
 create mode 100644 tests/keys/keyctl_relabel.c
 create mode 100755 tests/keys/test

Comments

Stephen Smalley Sept. 16, 2019, 5:58 p.m. UTC | #1
On 9/9/19 9:17 AM, Richard Haines wrote:
> Test all permissions associated with the key and key_socket classes.
> 
> Note that kernel 5.3 commit keys: Fix request_key() lack of Link perm
> check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
> added an additional check for link perm on request_key(). The tests
> will support earlier kernels.

I'm not sure why you coupled key and key_socket together; they don't 
have anything to do with each other, and were introduced in very 
different kernel and probably refpolicy releases.  I would recommend 
splitting them.  SECCLASS_KEY and its permission checks were introduced 
in Linux v2.6.18; SECCLASS_KEY_SOCKET was part of the original SELinux 
merge for Linux 2.6.0.

You only appear to be testing self access, not permission checks between 
a process and a keyring created by another process in a different 
security context.

1 test fails for me,
keys/test ................... Failed KEYCTL_SESSION_TO_PARENT: Operation 
not permitted
keys/test ................... 1/13
#   Failed test at keys/test line 38.
# Looks like you failed 1 test of 13.
keys/test ................... Dubious, test returned 1 (wstat 256, 0x100)

> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>   README.md                   |   3 +-
>   defconfig                   |   8 ++
>   policy/Makefile             |   4 +
>   policy/test_keys.te         | 161 ++++++++++++++++++++++++
>   tests/Makefile              |   4 +
>   tests/keys/.gitignore       |   3 +
>   tests/keys/Makefile         |   8 ++
>   tests/keys/key_sock.c       |  67 ++++++++++
>   tests/keys/keyctl.c         | 241 ++++++++++++++++++++++++++++++++++++
>   tests/keys/keyctl_relabel.c |  93 ++++++++++++++
>   tests/keys/test             |  98 +++++++++++++++
>   11 files changed, 689 insertions(+), 1 deletion(-)
>   create mode 100644 policy/test_keys.te
>   create mode 100644 tests/keys/.gitignore
>   create mode 100644 tests/keys/Makefile
>   create mode 100644 tests/keys/key_sock.c
>   create mode 100644 tests/keys/keyctl.c
>   create mode 100644 tests/keys/keyctl_relabel.c
>   create mode 100755 tests/keys/test
> 
> diff --git a/README.md b/README.md
> index 26784f8..fe72a91 100644
> --- a/README.md
> +++ b/README.md
> @@ -65,7 +65,8 @@ following command:
>   		netlabel_tools \
>   		iptables \
>   		lksctp-tools-devel \
> -		attr
> +		attr \
> +		keyutils-libs-devel
>   
>   The testsuite requires a pre-existing base policy configuration of SELinux,
>   using either the old example policy or the reference policy as the baseline.
> diff --git a/defconfig b/defconfig
> index d7f0ea5..c00e291 100644
> --- a/defconfig
> +++ b/defconfig
> @@ -62,3 +62,11 @@ CONFIG_ANDROID_BINDER_IPC=y
>   # This will configure the Dynamically Allocated Binder Devices added
>   # to 5.0+ kernels:
>   CONFIG_ANDROID_BINDERFS=y
> +
> +# Key implementations.
> +# These are enabled to test the key and key_socket controls in
> +# tests/keys; they are not required for SELinux operation itself.
> +CONFIG_KEYS=y
> +CONFIG_KEYS_COMPAT=y
> +CONFIG_KEY_DH_OPERATIONS=y
> +CONFIG_NET_KEY=m
> diff --git a/policy/Makefile b/policy/Makefile
> index 305b572..9258a93 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -71,6 +71,10 @@ ifeq ($(shell grep -q corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
>   TARGETS += test_sctp.te
>   endif
>   
> +ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && echo true),true)
> +TARGETS += test_keys.te
> +endif
> +
>   ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
>   TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
>   endif
> diff --git a/policy/test_keys.te b/policy/test_keys.te
> new file mode 100644
> index 0000000..9c65ec8
> --- /dev/null
> +++ b/policy/test_keys.te
> @@ -0,0 +1,161 @@
> +#
> +################# KEY selinux-testsuite policy module ######################
> +#
> +attribute keydomain;
> +
> +#
> +############################## Define Macro ################################
> +#
> +# Do not use domain_type() macro as it has allow 'key { link search }'
> +# in base module so 'allow domain self:key ~{ link search };' will not work
> +# here. Add these instead to allow key perms to be controlled by this module:
> +#
> +define(`key_domain_type',`
> +	allow $1 proc_t:dir { search };
> +	allow $1 proc_t:lnk_file { read };
> +	allow $1 self:dir { search };
> +	allow $1 self:file { open read write };
> +')
> +
> +#
> +####################### Main key class tests #####################
> +#
> +type test_key_t;
> +key_domain_type(test_key_t)
> +unconfined_runs_test(test_key_t)
> +typeattribute test_key_t testdomain;
> +typeattribute test_key_t keydomain;
> +
> +allow test_key_t self:process { setkeycreate };
> +allow test_key_t self:key { create write search read view link setattr };
> +
> +# Set new context on a keyring:
> +type test_newcon_key_t;
> +key_domain_type(test_newcon_key_t)
> +unconfined_runs_test(test_newcon_key_t)
> +typeattribute test_newcon_key_t testdomain;
> +typeattribute test_newcon_key_t keydomain;
> +
> +allow test_key_t test_newcon_key_t:key { create write search view };
> +
> +################# Deny process { setkeycreate } #######################
> +type test_no_setkeycreate_t;
> +key_domain_type(test_no_setkeycreate_t)
> +unconfined_runs_test(test_no_setkeycreate_t)
> +typeattribute test_no_setkeycreate_t testdomain;
> +typeattribute test_no_setkeycreate_t keydomain;
> +
> +###################### Deny key { create } ###########################
> +type test_key_no_create_t;
> +key_domain_type(test_key_no_create_t)
> +unconfined_runs_test(test_key_no_create_t)
> +typeattribute test_key_no_create_t testdomain;
> +typeattribute test_key_no_create_t keydomain;
> +
> +allow test_key_no_create_t self:process { setkeycreate };
> +allow test_key_no_create_t self:key { write search read view link setattr };
> +
> +###################### Deny key { write } ###########################
> +type test_key_no_write_t;
> +key_domain_type(test_key_no_write_t)
> +unconfined_runs_test(test_key_no_write_t)
> +typeattribute test_key_no_write_t testdomain;
> +typeattribute test_key_no_write_t keydomain;
> +
> +allow test_key_no_write_t self:process { setkeycreate };
> +allow test_key_no_write_t self:key { create search read view link setattr };
> +
> +###################### Deny key { search } ###########################
> +type test_key_no_search_t;
> +key_domain_type(test_key_no_search_t)
> +unconfined_runs_test(test_key_no_search_t)
> +typeattribute test_key_no_search_t testdomain;
> +typeattribute test_key_no_search_t keydomain;
> +
> +allow test_key_no_search_t self:process { setkeycreate };
> +allow test_key_no_search_t self:key { create write read view link setattr };
> +
> +###################### Deny key { view } ###########################
> +type test_key_no_view_t;
> +key_domain_type(test_key_no_view_t)
> +unconfined_runs_test(test_key_no_view_t)
> +typeattribute test_key_no_view_t testdomain;
> +typeattribute test_key_no_view_t keydomain;
> +
> +allow test_key_no_view_t self:process { setkeycreate };
> +allow test_key_no_view_t self:key { create write search read link setattr };
> +
> +###################### Deny key { read } ###########################
> +type test_key_no_read_t;
> +key_domain_type(test_key_no_read_t)
> +unconfined_runs_test(test_key_no_read_t)
> +typeattribute test_key_no_read_t testdomain;
> +typeattribute test_key_no_read_t keydomain;
> +
> +allow test_key_no_read_t self:process { setkeycreate };
> +allow test_key_no_read_t self:key { create write search view link setattr };
> +
> +###################### Deny key { link } ###########################
> +type test_key_no_link_t;
> +key_domain_type(test_key_no_link_t)
> +unconfined_runs_test(test_key_no_link_t)
> +typeattribute test_key_no_link_t testdomain;
> +typeattribute test_key_no_link_t keydomain;
> +
> +allow test_key_no_link_t self:process { setkeycreate };
> +allow test_key_no_link_t self:key { create write search read view setattr };
> +
> +###################### Deny key { setattr } ###########################
> +type test_key_no_setattr_t;
> +key_domain_type(test_key_no_setattr_t)
> +unconfined_runs_test(test_key_no_setattr_t)
> +typeattribute test_key_no_setattr_t testdomain;
> +typeattribute test_key_no_setattr_t keydomain;
> +
> +allow test_key_no_setattr_t self:process { setkeycreate };
> +allow test_key_no_setattr_t self:key { create write search read view link };
> +
> +#
> +######################## Test key_socket class ###########################
> +#
> +type test_key_sock_t;
> +domain_type(test_key_sock_t)
> +unconfined_runs_test(test_key_sock_t)
> +typeattribute test_key_sock_t testdomain;
> +typeattribute test_key_sock_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_t self:rawip_socket { create };
> +allow test_key_sock_t self:capability { net_admin };
> +allow test_key_sock_t self:key_socket { create write };
> +# For CONFIG_NET_KEY=m
> +allow test_key_sock_t kernel_t:system { module_request };
> +
> +################## Deny capability { net_admin } ##########################
> +type test_key_sock_no_net_admin_t;
> +domain_type(test_key_sock_no_net_admin_t)
> +unconfined_runs_test(test_key_sock_no_net_admin_t)
> +typeattribute test_key_sock_no_net_admin_t testdomain;
> +typeattribute test_key_sock_no_net_admin_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_no_net_admin_t self:rawip_socket { create };
> +allow test_key_sock_no_net_admin_t self:key_socket { create write };
> +
> +####################### Deny key_socket { create } ##########################
> +type test_key_sock_no_create_t;
> +domain_type(test_key_sock_no_create_t)
> +unconfined_runs_test(test_key_sock_no_create_t)
> +typeattribute test_key_sock_no_create_t testdomain;
> +typeattribute test_key_sock_no_create_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_no_create_t self:rawip_socket { create };
> +allow test_key_sock_no_create_t self:capability { net_admin };
> +allow test_key_sock_no_create_t self:key_socket { write };
> +
> +#
> +########### Allow these domains to be entered from sysadm domain ############
> +#
> +miscfiles_domain_entry_test_files(keydomain)
> +userdom_sysadm_entry_spec_domtrans_to(keydomain)
> diff --git a/tests/Makefile b/tests/Makefile
> index 63aa325..d1dbf38 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -42,6 +42,10 @@ ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && test -e
>   SUBDIRS += binder
>   endif
>   
> +ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && test -e $(INCLUDEDIR)/keyutils.h && echo true),true)
> +SUBDIRS += keys
> +endif
> +
>   ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
>   SUBDIRS += infiniband_endport
>   endif
> diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
> new file mode 100644
> index 0000000..4a0a48d
> --- /dev/null
> +++ b/tests/keys/.gitignore
> @@ -0,0 +1,3 @@
> +keyctl
> +keyctl_relabel
> +key_sock
> diff --git a/tests/keys/Makefile b/tests/keys/Makefile
> new file mode 100644
> index 0000000..3a00df5
> --- /dev/null
> +++ b/tests/keys/Makefile
> @@ -0,0 +1,8 @@
> +TARGETS = keyctl key_sock keyctl_relabel
> +
> +LDLIBS += -lselinux -lkeyutils
> +
> +all: $(TARGETS)
> +
> +clean:
> +	rm -f $(TARGETS)
> diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
> new file mode 100644
> index 0000000..8ac1f45
> --- /dev/null
> +++ b/tests/keys/key_sock.c
> @@ -0,0 +1,67 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <sys/socket.h>
> +#include <linux/pfkeyv2.h>
> +#include <selinux/selinux.h>
> +
> +static void 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[])
> +{
> +	char *context;
> +	int opt, sock, result;
> +	bool verbose = false;
> +
> +	while ((opt = getopt(argc, argv, "v")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	result = getcon(&context);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain process context\n");
> +		exit(-1);
> +	}
> +	if (verbose)
> +		printf("Process context: %s\n", context);
> +
> +	sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
> +	if (sock < 0) {
> +		fprintf(stderr, "Failed to open PF_KEY socket: %s\n",
> +			strerror(errno));
> +		exit(errno);
> +	}
> +	if (verbose)
> +		printf("Opened PF_KEY socket\n");
> +
> +	/* Write nothing to socket for test, expect EMSGSIZE error */
> +	result = write(sock, NULL, 0);
> +	if (result < 0 && errno == EMSGSIZE) {
> +		result = 0;
> +		if (verbose)
> +			printf("Written to PF_KEY socket\n");
> +	} else if (result < 0 && errno != EMSGSIZE) {
> +		result = -1;
> +		fprintf(stderr, "Failed write to PF_KEY socket: %s\n",
> +			strerror(errno));
> +	}
> +
> +	close(sock);
> +	return result;
> +}
> diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
> new file mode 100644
> index 0000000..6d85be7
> --- /dev/null
> +++ b/tests/keys/keyctl.c
> @@ -0,0 +1,241 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <keyutils.h>
> +#include <selinux/selinux.h>
> +
> +/* This is used as the payload for each add_key() */
> +static const char payload[] =
> +	" -----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\nWw4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
> +
> +static void 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, result;
> +	char *context, *keycreate_con;
> +	char r_con[256];
> +	bool verbose = false;
> +	key_serial_t retrieved, search, link, compute, newring,
> +		     private, prime, base, test_key;
> +	struct keyctl_dh_params params;
> +
> +	while ((opt = getopt(argc, argv, "v")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	result = getcon(&context);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain process context\n");
> +		exit(1);
> +	}
> +	if (verbose)
> +		printf("Process context: %s\n", context);
> +
> +	result = getkeycreatecon(&keycreate_con);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain keycreate context\n");
> +		exit(2);
> +	}
> +	if (verbose)
> +		printf("Current keycreate context: %s\n", keycreate_con);
> +	free(keycreate_con);
> +
> +	/* Set context requires process { setkeycreate } and key { create } */
> +	result = setkeycreatecon(context);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> +			strerror(errno));
> +		exit(3);
> +	}
> +	if (verbose)
> +		printf("Set keycreate context: %s\n", context);
> +	free(context);
> +
> +	result = getkeycreatecon(&keycreate_con);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain keycreate context\n");
> +		exit(4);
> +	}
> +	if (verbose)
> +		printf("New keycreate context: %s\n", keycreate_con);
> +	free(keycreate_con);
> +
> +	/*
> +	 * Add three keys as these will be required by the
> +	 * keyctl(KEYCTL_DH_COMPUTE, ..) function.
> +	 * These require key { create write } permissions.
> +	 */
> +	private = add_key("user", "private", payload, strlen(payload),
> +			  KEY_SPEC_PROCESS_KEYRING);
> +	if (private < 0) {
> +		fprintf(stderr, "Failed add_key(private): %s\n",
> +			strerror(errno));
> +		exit(5);
> +	}
> +
> +	prime = add_key("user", "prime", payload, strlen(payload),
> +			KEY_SPEC_PROCESS_KEYRING);
> +	if (prime < 0) {
> +		fprintf(stderr, "Failed add_key(prime): %s\n",
> +			strerror(errno));
> +		exit(6);
> +	}
> +
> +	base = add_key("user", "base", payload, strlen(payload),
> +		       KEY_SPEC_PROCESS_KEYRING);
> +	if (base < 0) {
> +		fprintf(stderr, "Failed add_key(base): %s\n",
> +			strerror(errno));
> +		exit(7);
> +	}
> +
> +	if (verbose) {
> +		printf("Private key ID: 0x%x\n", private);
> +		printf("Prime key ID:   0x%x\n", prime);
> +		printf("Base key ID:    0x%x\n", base);
> +	}
> +
> +	/* Requires key { search }. From kernel 5.3 also requires { link } */
> +	retrieved = request_key("user", "private", NULL,
> +				KEY_SPEC_PROCESS_KEYRING);
> +	if (retrieved < 0) {
> +		fprintf(stderr, "Failed to request 'private' key: %s\n",
> +			strerror(errno));
> +		exit(8);
> +	}
> +
> +	/* Requires key { search } */
> +	search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING, "user",
> +			"base", 0);
> +	if (search < 0) {
> +		fprintf(stderr, "Failed to find 'base' key: %s\n",
> +			strerror(errno));
> +		exit(9);
> +	}
> +
> +	/* Requires key { view } */
> +	result = keyctl(KEYCTL_GET_SECURITY, search, r_con, sizeof(r_con));
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain key context: %s\n",
> +			strerror(errno));
> +		exit(10);
> +	}
> +
> +	if (verbose) {
> +		printf("Requested 'private' key ID: 0x%x\n", retrieved);
> +		printf("Searched 'base' key ID:     0x%x\n", search);
> +		printf("Searched 'base' key context:\n\t%s\n", r_con);
> +	}
> +
> +	/* Compute DH key, only obtain the length for test, not the key. */
> +	params.priv = private;
> +	params.prime = prime;
> +	params.base = base;
> +
> +	/* Requires key { create read write } */
> +	compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
> +	if (compute < 0) {
> +		fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
> +			strerror(errno));
> +		exit(11);
> +	}
> +	if (verbose)
> +		printf("KEYCTL_DH_COMPUTE key ID size: %d\n", compute);
> +
> +	/* To test key { link }, need to generate a new keyring ID first */
> +	newring = add_key("keyring", "my-keyring", NULL, 0,
> +			  KEY_SPEC_THREAD_KEYRING);
> +	if (newring < 0) {
> +		fprintf(stderr, "Failed to add new keyring: %s\n",
> +			strerror(errno));
> +		exit(12);
> +	}
> +	if (verbose)
> +		printf("New keyring ID: 0x%x\n", newring);
> +
> +	/* Requires key { write link } */
> +	link = keyctl(KEYCTL_LINK, base, newring);
> +	if (link < 0) {
> +		fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
> +			strerror(errno));
> +		exit(13);
> +	}
> +	if (verbose)
> +		printf("Link key ID:    0x%x\n", newring);
> +
> +	/* Requires key { setattr } */
> +	link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL, NULL);
> +	if (link < 0) {
> +		fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING: %s\n",
> +			strerror(errno));
> +		exit(14);
> +	}
> +	if (verbose)
> +		printf("Restricted keyring\n");
> +
> +	/* Requires key { search } from 5.X key { inval } */
> +	test_key = keyctl(KEYCTL_INVALIDATE, private);
> +	if (test_key < 0) {
> +		fprintf(stderr, "Failed KEYCTL_INVALIDATE(private): %s\n",
> +			strerror(errno));
> +		exit(15);
> +	}
> +	if (verbose)
> +		printf("Invalidated 'private' key\n");
> +
> +	/* Requires key { write setattr } from 5.X key { revoke } */
> +	test_key = keyctl(KEYCTL_REVOKE, prime);
> +	if (test_key < 0) {
> +		fprintf(stderr, "Failed KEYCTL_REVOKE(prime): %s\n",
> +			strerror(errno));
> +		exit(16);
> +	}
> +	if (verbose)
> +		printf("Revoked 'prime' key\n");
> +
> +	/* Requires key { write } from 5.X key { clear } */
> +	test_key = keyctl(KEYCTL_CLEAR, newring);
> +	if (test_key < 0) {
> +		fprintf(stderr, "Failed KEYCTL_CLEAR(newring): %s\n",
> +			strerror(errno));
> +		exit(17);
> +	}
> +	if (verbose)
> +		printf("Cleared 'newring' keyring\n");
> +
> +	/* To test key { join }, need to join session first */
> +	test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
> +	if (test_key < 0) {
> +		fprintf(stderr, "Failed KEYCTL_JOIN_SESSION_KEYRING,: %s\n",
> +			strerror(errno));
> +		exit(18);
> +	}
> +	/* Requires key { link } from 5.X key { join } */
> +	test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
> +	if (test_key < 0) {
> +		fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT: %s\n",
> +			strerror(errno));
> +		exit(19);
> +	}
> +	if (verbose)
> +		printf("Joined session to parent\n");
> +
> +	return 0;
> +}
> diff --git a/tests/keys/keyctl_relabel.c b/tests/keys/keyctl_relabel.c
> new file mode 100644
> index 0000000..0276c7a
> --- /dev/null
> +++ b/tests/keys/keyctl_relabel.c
> @@ -0,0 +1,93 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <keyutils.h>
> +#include <selinux/selinux.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-v] newcon\n"
> +		"Where:\n\t"
> +		"-v      Print information.\n\t"
> +		"newcon  New keyring context.\n", progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int opt, result;
> +	char *context, *keycreate_con;
> +	char r_con[256];
> +	bool verbose = false;
> +	key_serial_t newring;
> +
> +	while ((opt = getopt(argc, argv, "v")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc)
> +		usage(argv[0]);
> +
> +	result = getcon(&context);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain process context\n");
> +		exit(1);
> +	}
> +	if (verbose)
> +		printf("Process context: %s\n", context);
> +	free(context);
> +
> +	result = setkeycreatecon(argv[optind]);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> +			strerror(errno));
> +		exit(2);
> +	}
> +
> +	result = getkeycreatecon(&keycreate_con);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain keycreate context\n");
> +		exit(3);
> +	}
> +	if (verbose)
> +		printf("New keycreate context: %s\n", keycreate_con);
> +	free(keycreate_con);
> +
> +	newring = add_key("keyring", "my-keyring", NULL, 0,
> +			  KEY_SPEC_THREAD_KEYRING);
> +	if (newring < 0) {
> +		fprintf(stderr, "Failed to add new keyring: %s\n",
> +			strerror(errno));
> +		exit(4);
> +	}
> +
> +	result = keyctl(KEYCTL_GET_SECURITY, newring, r_con, sizeof(r_con));
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain key context: %s\n",
> +			strerror(errno));
> +		exit(5);
> +	}
> +
> +	if (strcmp(argv[optind], r_con)) {
> +		fprintf(stderr, "Relabel error - expected: %s got: %s\n",
> +			argv[optind], r_con);
> +		exit(6);
> +	}
> +
> +	if (verbose) {
> +		printf("'my-keyring' key ID: 0x%x\n", newring);
> +		printf("'my-keyring' context:\n\t%s\n", r_con);
> +	}
> +
> +	return 0;
> +}
> diff --git a/tests/keys/test b/tests/keys/test
> new file mode 100755
> index 0000000..4916e7c
> --- /dev/null
> +++ b/tests/keys/test
> @@ -0,0 +1,98 @@
> +#!/usr/bin/perl
> +use Test::More;
> +
> +BEGIN {
> +    $basedir = $0;
> +    $basedir =~ s|(.*)/[^/]*|$1|;
> +
> +    $test_count = 13;
> +
> +    # 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 = " ";
> +    }
> +
> +    # From kernel 5.3 request_key() requires additional check of key { link }
> +    $kvercur = `uname -r`;
> +    chomp($kvercur);
> +    $kverminstream = "5.3";
> +    $test_link_53  = 0;
> +
> +    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
> +    if ( $result >= 0 ) {
> +        $test_link_53 = 1;
> +    }
> +
> +    plan tests => $test_count;
> +}
> +
> +############ Test keyctl #############
> +print "Test key class permissions\n";
> +$result = system "runcon -t test_key_t $basedir/keyctl $v";
> +ok( $result eq 0 );
> +
> +# Deny process { setkeycreate }
> +$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 3 );
> +
> +# Deny key { create }
> +$result = system "runcon -t test_key_no_create_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 3 );
> +
> +# Deny key { write }
> +$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 5 );
> +
> +# Deny key { search }
> +$result = system "runcon -t test_key_no_search_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# Deny key { view }
> +$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 10 );
> +
> +# Deny key { read }
> +$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 11 );
> +
> +# Deny key { link }
> +$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v 2>&1";
> +if ($test_link_53) {
> +    ok( $result >> 8 eq 8 );
> +}
> +else {
> +    ok( $result >> 8 eq 13 );
> +}
> +
> +# Deny key { setattr }
> +$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 14 );
> +
> +########### Change keyring context ##############
> +print "Change keyring context\n";
> +$result = system
> +"runcon -t test_key_t $basedir/keyctl_relabel $v system_u:system_r:test_newcon_key_t:s0";
> +ok( $result eq 0 );
> +
> +############ Test key_socket #############
> +print "Test key_socket class\n";
> +$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
> +ok( $result eq 0 );
> +
> +# Deny capability { net_admin } - EPERM
> +$result =
> +  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock $v 2>&1";
> +ok( $result >> 8 eq 1 );
> +
> +# Deny key_socket { create } - EACCES
> +$result =
> +  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v 2>&1";
> +ok( $result >> 8 eq 13 );
> +
> +exit;
>
Richard Haines Sept. 16, 2019, 6:55 p.m. UTC | #2
On Mon, 2019-09-16 at 13:58 -0400, Stephen Smalley wrote:
> On 9/9/19 9:17 AM, Richard Haines wrote:
> > Test all permissions associated with the key and key_socket
> > classes.
> > 
> > Note that kernel 5.3 commit keys: Fix request_key() lack of Link
> > perm
> > check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
> > added an additional check for link perm on request_key(). The tests
> > will support earlier kernels.
> 
> I'm not sure why you coupled key and key_socket together; they don't 
> have anything to do with each other, and were introduced in very 
> different kernel and probably refpolicy releases.  I would recommend 
> splitting them.  SECCLASS_KEY and its permission checks were
> introduced 
> in Linux v2.6.18; SECCLASS_KEY_SOCKET was part of the original
> SELinux 
> merge for Linux 2.6.0.

I'll split them.

> 
> You only appear to be testing self access, not permission checks
> between 
> a process and a keyring created by another process in a different 
> security context.

Okay I'll add these tests
> 
> 1 test fails for me,
> keys/test ................... Failed KEYCTL_SESSION_TO_PARENT:
> Operation 
> not permitted
> keys/test ................... 1/13
> #   Failed test at keys/test line 38.
> # Looks like you failed 1 test of 13.
> keys/test ................... Dubious, test returned 1 (wstat 256,
> 0x100)

You must have systems that don't like my patches - I can't get this
fail. Using Fedora 30 and also Rawhide from a few weeks ago.

I don't know if this is of any interest (It works on Rawhide with
kernel from [1]):

I've been building 'key' tests to add the new permissions defined in
kernel-next [1].
To test these with new policy supporting the new perms + old policy
that does not, I added the kernel test patch below.
This patch handles security_key_permission() passing a single
permission, as checking the current keys code I only see it passing a
single permission at a time.

I've also an sepol patch + selinux-testsuite tests

[1] 
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/security/selinux?h=next-20190904&id=1f96e0f129eb2bea15a00c154eee8b85aa181d1a


diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 901cc052f..9c8f90648 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6502,7 +6502,7 @@ static int selinux_key_permission(key_ref_t
key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
-	unsigned oldstyle_perm;
+	unsigned int key_perm = 0;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6511,18 +6511,57 @@ static int selinux_key_permission(key_ref_t
key_ref,
 	if (perm == 0)
 		return 0;
 
-	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ |
KEY_NEED_WRITE |
-				KEY_NEED_SEARCH | KEY_NEED_LINK);
-	if (perm & KEY_NEED_SETSEC)
-		oldstyle_perm |= OLD_KEY_NEED_SETATTR;
-	if (perm & KEY_NEED_INVAL)
-		oldstyle_perm |= KEY_NEED_SEARCH;
-	if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
-		oldstyle_perm |= KEY_NEED_WRITE;
-	if (perm & KEY_NEED_JOIN)
-		oldstyle_perm |= KEY_NEED_SEARCH;
-	if (perm & KEY_NEED_CLEAR)
-		oldstyle_perm |= KEY_NEED_WRITE;
+	/* selinux_key_permission() is called with only one permission
set. */
+	switch (perm) {
+	case KEY_NEED_VIEW:
+		key_perm = KEY__VIEW;
+		break;
+	case KEY_NEED_READ:
+		key_perm = KEY__READ;
+		break;
+	case KEY_NEED_WRITE:
+		key_perm = KEY__WRITE;
+		break;
+	case KEY_NEED_SEARCH:
+		key_perm = KEY__SEARCH;
+		break;
+	case KEY_NEED_LINK:
+		key_perm = KEY__LINK;
+		break;
+	case KEY_NEED_SETSEC: /* Keep this as "setattr" in policy */
+		key_perm = KEY__SETATTR;
+		break;
+	case KEY_NEED_INVAL:
+		key_perm = KEY__INVAL;
+		break;
+	case KEY_NEED_REVOKE:
+		key_perm = KEY__REVOKE;
+		break;
+	case KEY_NEED_JOIN:
+		key_perm = KEY__JOIN;
+		break;
+	case KEY_NEED_CLEAR:
+		key_perm = KEY__CLEAR;
+		break;
+	}
+
+	/* If old policy, then reset new perms to orig. */
+	if (!selinux_policycap_key_perms()) {
+		switch (perm) {
+		case KEY_NEED_INVAL:
+			key_perm = KEY__SEARCH;
+			break;
+		case KEY_NEED_REVOKE:
+			key_perm = KEY__WRITE;
+			break;
+		case KEY_NEED_JOIN:
+			key_perm = KEY__SEARCH;
+			break;
+		case KEY_NEED_CLEAR:
+			key_perm = KEY__WRITE;
+			break;
+		}
+	}
 
 	sid = cred_sid(cred);
 
@@ -6530,7 +6569,7 @@ static int selinux_key_permission(key_ref_t
key_ref,
 	ksec = key->security;
 
 	return avc_has_perm(&selinux_state,
-			    sid, ksec->sid, SECCLASS_KEY,
oldstyle_perm, NULL);
+			    sid, ksec->sid, SECCLASS_KEY, key_perm,
NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
@@ -6555,7 +6594,7 @@ static int selinux_watch_key(struct key *key)
 	u32 sid = current_sid();
 
 	return avc_has_perm(&selinux_state,
-			    sid, ksec->sid, SECCLASS_KEY,
KEY_NEED_VIEW, NULL);
+			    sid, ksec->sid, SECCLASS_KEY, KEY__VIEW,
NULL);
 }
 #endif
 #endif
diff --git a/security/selinux/include/classmap.h
b/security/selinux/include/classmap.h
index 201f7e588..a51ab9bd9 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -158,7 +158,7 @@ struct security_class_mapping secclass_map[] = {
 	  { "send", "recv", "relabelto", "forward_in", "forward_out",
NULL } },
 	{ "key",
 	  { "view", "read", "write", "search", "link", "setattr",
"create",
-	    NULL } },
+	    "inval", "revoke", "join", "clear", NULL } },
 	{ "dccp_socket",
 	  { COMMON_SOCK_PERMS,
 	    "node_bind", "name_connect", NULL } },
diff --git a/security/selinux/include/security.h
b/security/selinux/include/security.h
index 111121281..a248eef75 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -78,6 +78,7 @@ enum {
 	POLICYDB_CAPABILITY_ALWAYSNETWORK,
 	POLICYDB_CAPABILITY_CGROUPSECLABEL,
 	POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
+	POLICYDB_CAPABILITY_KEYPERMS,
 	__POLICYDB_CAPABILITY_MAX
 };
 #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
@@ -177,6 +178,13 @@ static inline bool
selinux_policycap_nnp_nosuid_transition(void)
 	return state-
>policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
 }
 
+static inline bool selinux_policycap_key_perms(void)
+{
+	struct selinux_state *state = &selinux_state;
+
+	return state->policycap[POLICYDB_CAPABILITY_KEYPERMS];
+}
+
 int security_mls_enabled(struct selinux_state *state);
 int security_load_policy(struct selinux_state *state,
 			 void *data, size_t len);
diff --git a/security/selinux/ss/services.c
b/security/selinux/ss/services.c
index d61563a36..eb3949fc8 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -73,7 +73,8 @@ const char
*selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
 	"extended_socket_class",
 	"always_check_network",
 	"cgroup_seclabel",
-	"nnp_nosuid_transition"
+	"nnp_nosuid_transition",
+	"key_perms"
 };
 
 static struct selinux_ss selinux_ss;



> 
> > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> > ---
> >   README.md                   |   3 +-
> >   defconfig                   |   8 ++
> >   policy/Makefile             |   4 +
> >   policy/test_keys.te         | 161 ++++++++++++++++++++++++
> >   tests/Makefile              |   4 +
> >   tests/keys/.gitignore       |   3 +
> >   tests/keys/Makefile         |   8 ++
> >   tests/keys/key_sock.c       |  67 ++++++++++
> >   tests/keys/keyctl.c         | 241
> > ++++++++++++++++++++++++++++++++++++
> >   tests/keys/keyctl_relabel.c |  93 ++++++++++++++
> >   tests/keys/test             |  98 +++++++++++++++
> >   11 files changed, 689 insertions(+), 1 deletion(-)
> >   create mode 100644 policy/test_keys.te
> >   create mode 100644 tests/keys/.gitignore
> >   create mode 100644 tests/keys/Makefile
> >   create mode 100644 tests/keys/key_sock.c
> >   create mode 100644 tests/keys/keyctl.c
> >   create mode 100644 tests/keys/keyctl_relabel.c
> >   create mode 100755 tests/keys/test
> > 
> > diff --git a/README.md b/README.md
> > index 26784f8..fe72a91 100644
> > --- a/README.md
> > +++ b/README.md
> > @@ -65,7 +65,8 @@ following command:
> >   		netlabel_tools \
> >   		iptables \
> >   		lksctp-tools-devel \
> > -		attr
> > +		attr \
> > +		keyutils-libs-devel
> >   
> >   The testsuite requires a pre-existing base policy configuration
> > of SELinux,
> >   using either the old example policy or the reference policy as
> > the baseline.
> > diff --git a/defconfig b/defconfig
> > index d7f0ea5..c00e291 100644
> > --- a/defconfig
> > +++ b/defconfig
> > @@ -62,3 +62,11 @@ CONFIG_ANDROID_BINDER_IPC=y
> >   # This will configure the Dynamically Allocated Binder Devices
> > added
> >   # to 5.0+ kernels:
> >   CONFIG_ANDROID_BINDERFS=y
> > +
> > +# Key implementations.
> > +# These are enabled to test the key and key_socket controls in
> > +# tests/keys; they are not required for SELinux operation itself.
> > +CONFIG_KEYS=y
> > +CONFIG_KEYS_COMPAT=y
> > +CONFIG_KEY_DH_OPERATIONS=y
> > +CONFIG_NET_KEY=m
> > diff --git a/policy/Makefile b/policy/Makefile
> > index 305b572..9258a93 100644
> > --- a/policy/Makefile
> > +++ b/policy/Makefile
> > @@ -71,6 +71,10 @@ ifeq ($(shell grep -q
> > corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
> >   TARGETS += test_sctp.te
> >   endif
> >   
> > +ifeq ($(shell grep -q key_socket
> > $(POLDEV)/include/support/all_perms.spt && echo true),true)
> > +TARGETS += test_keys.te
> > +endif
> > +
> >   ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
> >   TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te,
> > $(TARGETS))
> >   endif
> > diff --git a/policy/test_keys.te b/policy/test_keys.te
> > new file mode 100644
> > index 0000000..9c65ec8
> > --- /dev/null
> > +++ b/policy/test_keys.te
> > @@ -0,0 +1,161 @@
> > +#
> > +################# KEY selinux-testsuite policy module
> > ######################
> > +#
> > +attribute keydomain;
> > +
> > +#
> > +############################## Define Macro
> > ################################
> > +#
> > +# Do not use domain_type() macro as it has allow 'key { link
> > search }'
> > +# in base module so 'allow domain self:key ~{ link search };' will
> > not work
> > +# here. Add these instead to allow key perms to be controlled by
> > this module:
> > +#
> > +define(`key_domain_type',`
> > +	allow $1 proc_t:dir { search };
> > +	allow $1 proc_t:lnk_file { read };
> > +	allow $1 self:dir { search };
> > +	allow $1 self:file { open read write };
> > +')
> > +
> > +#
> > +####################### Main key class tests #####################
> > +#
> > +type test_key_t;
> > +key_domain_type(test_key_t)
> > +unconfined_runs_test(test_key_t)
> > +typeattribute test_key_t testdomain;
> > +typeattribute test_key_t keydomain;
> > +
> > +allow test_key_t self:process { setkeycreate };
> > +allow test_key_t self:key { create write search read view link
> > setattr };
> > +
> > +# Set new context on a keyring:
> > +type test_newcon_key_t;
> > +key_domain_type(test_newcon_key_t)
> > +unconfined_runs_test(test_newcon_key_t)
> > +typeattribute test_newcon_key_t testdomain;
> > +typeattribute test_newcon_key_t keydomain;
> > +
> > +allow test_key_t test_newcon_key_t:key { create write search view
> > };
> > +
> > +################# Deny process { setkeycreate }
> > #######################
> > +type test_no_setkeycreate_t;
> > +key_domain_type(test_no_setkeycreate_t)
> > +unconfined_runs_test(test_no_setkeycreate_t)
> > +typeattribute test_no_setkeycreate_t testdomain;
> > +typeattribute test_no_setkeycreate_t keydomain;
> > +
> > +###################### Deny key { create }
> > ###########################
> > +type test_key_no_create_t;
> > +key_domain_type(test_key_no_create_t)
> > +unconfined_runs_test(test_key_no_create_t)
> > +typeattribute test_key_no_create_t testdomain;
> > +typeattribute test_key_no_create_t keydomain;
> > +
> > +allow test_key_no_create_t self:process { setkeycreate };
> > +allow test_key_no_create_t self:key { write search read view link
> > setattr };
> > +
> > +###################### Deny key { write }
> > ###########################
> > +type test_key_no_write_t;
> > +key_domain_type(test_key_no_write_t)
> > +unconfined_runs_test(test_key_no_write_t)
> > +typeattribute test_key_no_write_t testdomain;
> > +typeattribute test_key_no_write_t keydomain;
> > +
> > +allow test_key_no_write_t self:process { setkeycreate };
> > +allow test_key_no_write_t self:key { create search read view link
> > setattr };
> > +
> > +###################### Deny key { search }
> > ###########################
> > +type test_key_no_search_t;
> > +key_domain_type(test_key_no_search_t)
> > +unconfined_runs_test(test_key_no_search_t)
> > +typeattribute test_key_no_search_t testdomain;
> > +typeattribute test_key_no_search_t keydomain;
> > +
> > +allow test_key_no_search_t self:process { setkeycreate };
> > +allow test_key_no_search_t self:key { create write read view link
> > setattr };
> > +
> > +###################### Deny key { view }
> > ###########################
> > +type test_key_no_view_t;
> > +key_domain_type(test_key_no_view_t)
> > +unconfined_runs_test(test_key_no_view_t)
> > +typeattribute test_key_no_view_t testdomain;
> > +typeattribute test_key_no_view_t keydomain;
> > +
> > +allow test_key_no_view_t self:process { setkeycreate };
> > +allow test_key_no_view_t self:key { create write search read link
> > setattr };
> > +
> > +###################### Deny key { read }
> > ###########################
> > +type test_key_no_read_t;
> > +key_domain_type(test_key_no_read_t)
> > +unconfined_runs_test(test_key_no_read_t)
> > +typeattribute test_key_no_read_t testdomain;
> > +typeattribute test_key_no_read_t keydomain;
> > +
> > +allow test_key_no_read_t self:process { setkeycreate };
> > +allow test_key_no_read_t self:key { create write search view link
> > setattr };
> > +
> > +###################### Deny key { link }
> > ###########################
> > +type test_key_no_link_t;
> > +key_domain_type(test_key_no_link_t)
> > +unconfined_runs_test(test_key_no_link_t)
> > +typeattribute test_key_no_link_t testdomain;
> > +typeattribute test_key_no_link_t keydomain;
> > +
> > +allow test_key_no_link_t self:process { setkeycreate };
> > +allow test_key_no_link_t self:key { create write search read view
> > setattr };
> > +
> > +###################### Deny key { setattr }
> > ###########################
> > +type test_key_no_setattr_t;
> > +key_domain_type(test_key_no_setattr_t)
> > +unconfined_runs_test(test_key_no_setattr_t)
> > +typeattribute test_key_no_setattr_t testdomain;
> > +typeattribute test_key_no_setattr_t keydomain;
> > +
> > +allow test_key_no_setattr_t self:process { setkeycreate };
> > +allow test_key_no_setattr_t self:key { create write search read
> > view link };
> > +
> > +#
> > +######################## Test key_socket class
> > ###########################
> > +#
> > +type test_key_sock_t;
> > +domain_type(test_key_sock_t)
> > +unconfined_runs_test(test_key_sock_t)
> > +typeattribute test_key_sock_t testdomain;
> > +typeattribute test_key_sock_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_t self:rawip_socket { create };
> > +allow test_key_sock_t self:capability { net_admin };
> > +allow test_key_sock_t self:key_socket { create write };
> > +# For CONFIG_NET_KEY=m
> > +allow test_key_sock_t kernel_t:system { module_request };
> > +
> > +################## Deny capability { net_admin }
> > ##########################
> > +type test_key_sock_no_net_admin_t;
> > +domain_type(test_key_sock_no_net_admin_t)
> > +unconfined_runs_test(test_key_sock_no_net_admin_t)
> > +typeattribute test_key_sock_no_net_admin_t testdomain;
> > +typeattribute test_key_sock_no_net_admin_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_no_net_admin_t self:rawip_socket { create };
> > +allow test_key_sock_no_net_admin_t self:key_socket { create write
> > };
> > +
> > +####################### Deny key_socket { create }
> > ##########################
> > +type test_key_sock_no_create_t;
> > +domain_type(test_key_sock_no_create_t)
> > +unconfined_runs_test(test_key_sock_no_create_t)
> > +typeattribute test_key_sock_no_create_t testdomain;
> > +typeattribute test_key_sock_no_create_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_no_create_t self:rawip_socket { create };
> > +allow test_key_sock_no_create_t self:capability { net_admin };
> > +allow test_key_sock_no_create_t self:key_socket { write };
> > +
> > +#
> > +########### Allow these domains to be entered from sysadm domain
> > ############
> > +#
> > +miscfiles_domain_entry_test_files(keydomain)
> > +userdom_sysadm_entry_spec_domtrans_to(keydomain)
> > diff --git a/tests/Makefile b/tests/Makefile
> > index 63aa325..d1dbf38 100644
> > --- a/tests/Makefile
> > +++ b/tests/Makefile
> > @@ -42,6 +42,10 @@ ifeq ($(shell grep -q binder
> > $(POLDEV)/include/support/all_perms.spt && test -e
> >   SUBDIRS += binder
> >   endif
> >   
> > +ifeq ($(shell grep -q key_socket
> > $(POLDEV)/include/support/all_perms.spt && test -e
> > $(INCLUDEDIR)/keyutils.h && echo true),true)
> > +SUBDIRS += keys
> > +endif
> > +
> >   ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST="
> > infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
> >   SUBDIRS += infiniband_endport
> >   endif
> > diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
> > new file mode 100644
> > index 0000000..4a0a48d
> > --- /dev/null
> > +++ b/tests/keys/.gitignore
> > @@ -0,0 +1,3 @@
> > +keyctl
> > +keyctl_relabel
> > +key_sock
> > diff --git a/tests/keys/Makefile b/tests/keys/Makefile
> > new file mode 100644
> > index 0000000..3a00df5
> > --- /dev/null
> > +++ b/tests/keys/Makefile
> > @@ -0,0 +1,8 @@
> > +TARGETS = keyctl key_sock keyctl_relabel
> > +
> > +LDLIBS += -lselinux -lkeyutils
> > +
> > +all: $(TARGETS)
> > +
> > +clean:
> > +	rm -f $(TARGETS)
> > diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
> > new file mode 100644
> > index 0000000..8ac1f45
> > --- /dev/null
> > +++ b/tests/keys/key_sock.c
> > @@ -0,0 +1,67 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <sys/socket.h>
> > +#include <linux/pfkeyv2.h>
> > +#include <selinux/selinux.h>
> > +
> > +static void 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[])
> > +{
> > +	char *context;
> > +	int opt, sock, result;
> > +	bool verbose = false;
> > +
> > +	while ((opt = getopt(argc, argv, "v")) != -1) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	result = getcon(&context);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain process context\n");
> > +		exit(-1);
> > +	}
> > +	if (verbose)
> > +		printf("Process context: %s\n", context);
> > +
> > +	sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
> > +	if (sock < 0) {
> > +		fprintf(stderr, "Failed to open PF_KEY socket: %s\n",
> > +			strerror(errno));
> > +		exit(errno);
> > +	}
> > +	if (verbose)
> > +		printf("Opened PF_KEY socket\n");
> > +
> > +	/* Write nothing to socket for test, expect EMSGSIZE error */
> > +	result = write(sock, NULL, 0);
> > +	if (result < 0 && errno == EMSGSIZE) {
> > +		result = 0;
> > +		if (verbose)
> > +			printf("Written to PF_KEY socket\n");
> > +	} else if (result < 0 && errno != EMSGSIZE) {
> > +		result = -1;
> > +		fprintf(stderr, "Failed write to PF_KEY socket: %s\n",
> > +			strerror(errno));
> > +	}
> > +
> > +	close(sock);
> > +	return result;
> > +}
> > diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
> > new file mode 100644
> > index 0000000..6d85be7
> > --- /dev/null
> > +++ b/tests/keys/keyctl.c
> > @@ -0,0 +1,241 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <keyutils.h>
> > +#include <selinux/selinux.h>
> > +
> > +/* This is used as the payload for each add_key() */
> > +static const char payload[] =
> > +	" -----BEGIN PUBLIC KEY-----
> > \nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\
> > nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\n
> > Ww4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4
> > aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
> > +
> > +static void 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, result;
> > +	char *context, *keycreate_con;
> > +	char r_con[256];
> > +	bool verbose = false;
> > +	key_serial_t retrieved, search, link, compute, newring,
> > +		     private, prime, base, test_key;
> > +	struct keyctl_dh_params params;
> > +
> > +	while ((opt = getopt(argc, argv, "v")) != -1) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	result = getcon(&context);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain process context\n");
> > +		exit(1);
> > +	}
> > +	if (verbose)
> > +		printf("Process context: %s\n", context);
> > +
> > +	result = getkeycreatecon(&keycreate_con);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +		exit(2);
> > +	}
> > +	if (verbose)
> > +		printf("Current keycreate context: %s\n",
> > keycreate_con);
> > +	free(keycreate_con);
> > +
> > +	/* Set context requires process { setkeycreate } and key {
> > create } */
> > +	result = setkeycreatecon(context);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> > +			strerror(errno));
> > +		exit(3);
> > +	}
> > +	if (verbose)
> > +		printf("Set keycreate context: %s\n", context);
> > +	free(context);
> > +
> > +	result = getkeycreatecon(&keycreate_con);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +		exit(4);
> > +	}
> > +	if (verbose)
> > +		printf("New keycreate context: %s\n", keycreate_con);
> > +	free(keycreate_con);
> > +
> > +	/*
> > +	 * Add three keys as these will be required by the
> > +	 * keyctl(KEYCTL_DH_COMPUTE, ..) function.
> > +	 * These require key { create write } permissions.
> > +	 */
> > +	private = add_key("user", "private", payload, strlen(payload),
> > +			  KEY_SPEC_PROCESS_KEYRING);
> > +	if (private < 0) {
> > +		fprintf(stderr, "Failed add_key(private): %s\n",
> > +			strerror(errno));
> > +		exit(5);
> > +	}
> > +
> > +	prime = add_key("user", "prime", payload, strlen(payload),
> > +			KEY_SPEC_PROCESS_KEYRING);
> > +	if (prime < 0) {
> > +		fprintf(stderr, "Failed add_key(prime): %s\n",
> > +			strerror(errno));
> > +		exit(6);
> > +	}
> > +
> > +	base = add_key("user", "base", payload, strlen(payload),
> > +		       KEY_SPEC_PROCESS_KEYRING);
> > +	if (base < 0) {
> > +		fprintf(stderr, "Failed add_key(base): %s\n",
> > +			strerror(errno));
> > +		exit(7);
> > +	}
> > +
> > +	if (verbose) {
> > +		printf("Private key ID: 0x%x\n", private);
> > +		printf("Prime key ID:   0x%x\n", prime);
> > +		printf("Base key ID:    0x%x\n", base);
> > +	}
> > +
> > +	/* Requires key { search }. From kernel 5.3 also requires {
> > link } */
> > +	retrieved = request_key("user", "private", NULL,
> > +				KEY_SPEC_PROCESS_KEYRING);
> > +	if (retrieved < 0) {
> > +		fprintf(stderr, "Failed to request 'private' key:
> > %s\n",
> > +			strerror(errno));
> > +		exit(8);
> > +	}
> > +
> > +	/* Requires key { search } */
> > +	search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING,
> > "user",
> > +			"base", 0);
> > +	if (search < 0) {
> > +		fprintf(stderr, "Failed to find 'base' key: %s\n",
> > +			strerror(errno));
> > +		exit(9);
> > +	}
> > +
> > +	/* Requires key { view } */
> > +	result = keyctl(KEYCTL_GET_SECURITY, search, r_con,
> > sizeof(r_con));
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain key context: %s\n",
> > +			strerror(errno));
> > +		exit(10);
> > +	}
> > +
> > +	if (verbose) {
> > +		printf("Requested 'private' key ID: 0x%x\n",
> > retrieved);
> > +		printf("Searched 'base' key ID:     0x%x\n", search);
> > +		printf("Searched 'base' key context:\n\t%s\n", r_con);
> > +	}
> > +
> > +	/* Compute DH key, only obtain the length for test, not the
> > key. */
> > +	params.priv = private;
> > +	params.prime = prime;
> > +	params.base = base;
> > +
> > +	/* Requires key { create read write } */
> > +	compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
> > +	if (compute < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
> > +			strerror(errno));
> > +		exit(11);
> > +	}
> > +	if (verbose)
> > +		printf("KEYCTL_DH_COMPUTE key ID size: %d\n", compute);
> > +
> > +	/* To test key { link }, need to generate a new keyring ID
> > first */
> > +	newring = add_key("keyring", "my-keyring", NULL, 0,
> > +			  KEY_SPEC_THREAD_KEYRING);
> > +	if (newring < 0) {
> > +		fprintf(stderr, "Failed to add new keyring: %s\n",
> > +			strerror(errno));
> > +		exit(12);
> > +	}
> > +	if (verbose)
> > +		printf("New keyring ID: 0x%x\n", newring);
> > +
> > +	/* Requires key { write link } */
> > +	link = keyctl(KEYCTL_LINK, base, newring);
> > +	if (link < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
> > +			strerror(errno));
> > +		exit(13);
> > +	}
> > +	if (verbose)
> > +		printf("Link key ID:    0x%x\n", newring);
> > +
> > +	/* Requires key { setattr } */
> > +	link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL, NULL);
> > +	if (link < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING: %s\n",
> > +			strerror(errno));
> > +		exit(14);
> > +	}
> > +	if (verbose)
> > +		printf("Restricted keyring\n");
> > +
> > +	/* Requires key { search } from 5.X key { inval } */
> > +	test_key = keyctl(KEYCTL_INVALIDATE, private);
> > +	if (test_key < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_INVALIDATE(private):
> > %s\n",
> > +			strerror(errno));
> > +		exit(15);
> > +	}
> > +	if (verbose)
> > +		printf("Invalidated 'private' key\n");
> > +
> > +	/* Requires key { write setattr } from 5.X key { revoke } */
> > +	test_key = keyctl(KEYCTL_REVOKE, prime);
> > +	if (test_key < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_REVOKE(prime): %s\n",
> > +			strerror(errno));
> > +		exit(16);
> > +	}
> > +	if (verbose)
> > +		printf("Revoked 'prime' key\n");
> > +
> > +	/* Requires key { write } from 5.X key { clear } */
> > +	test_key = keyctl(KEYCTL_CLEAR, newring);
> > +	if (test_key < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_CLEAR(newring): %s\n",
> > +			strerror(errno));
> > +		exit(17);
> > +	}
> > +	if (verbose)
> > +		printf("Cleared 'newring' keyring\n");
> > +
> > +	/* To test key { join }, need to join session first */
> > +	test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
> > +	if (test_key < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_JOIN_SESSION_KEYRING,:
> > %s\n",
> > +			strerror(errno));
> > +		exit(18);
> > +	}
> > +	/* Requires key { link } from 5.X key { join } */
> > +	test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
> > +	if (test_key < 0) {
> > +		fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT:
> > %s\n",
> > +			strerror(errno));
> > +		exit(19);
> > +	}
> > +	if (verbose)
> > +		printf("Joined session to parent\n");
> > +
> > +	return 0;
> > +}
> > diff --git a/tests/keys/keyctl_relabel.c
> > b/tests/keys/keyctl_relabel.c
> > new file mode 100644
> > index 0000000..0276c7a
> > --- /dev/null
> > +++ b/tests/keys/keyctl_relabel.c
> > @@ -0,0 +1,93 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <keyutils.h>
> > +#include <selinux/selinux.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"usage:  %s [-v] newcon\n"
> > +		"Where:\n\t"
> > +		"-v      Print information.\n\t"
> > +		"newcon  New keyring context.\n", progname);
> > +	exit(-1);
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +	int opt, result;
> > +	char *context, *keycreate_con;
> > +	char r_con[256];
> > +	bool verbose = false;
> > +	key_serial_t newring;
> > +
> > +	while ((opt = getopt(argc, argv, "v")) != -1) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc)
> > +		usage(argv[0]);
> > +
> > +	result = getcon(&context);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain process context\n");
> > +		exit(1);
> > +	}
> > +	if (verbose)
> > +		printf("Process context: %s\n", context);
> > +	free(context);
> > +
> > +	result = setkeycreatecon(argv[optind]);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> > +			strerror(errno));
> > +		exit(2);
> > +	}
> > +
> > +	result = getkeycreatecon(&keycreate_con);
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +		exit(3);
> > +	}
> > +	if (verbose)
> > +		printf("New keycreate context: %s\n", keycreate_con);
> > +	free(keycreate_con);
> > +
> > +	newring = add_key("keyring", "my-keyring", NULL, 0,
> > +			  KEY_SPEC_THREAD_KEYRING);
> > +	if (newring < 0) {
> > +		fprintf(stderr, "Failed to add new keyring: %s\n",
> > +			strerror(errno));
> > +		exit(4);
> > +	}
> > +
> > +	result = keyctl(KEYCTL_GET_SECURITY, newring, r_con,
> > sizeof(r_con));
> > +	if (result < 0) {
> > +		fprintf(stderr, "Failed to obtain key context: %s\n",
> > +			strerror(errno));
> > +		exit(5);
> > +	}
> > +
> > +	if (strcmp(argv[optind], r_con)) {
> > +		fprintf(stderr, "Relabel error - expected: %s got:
> > %s\n",
> > +			argv[optind], r_con);
> > +		exit(6);
> > +	}
> > +
> > +	if (verbose) {
> > +		printf("'my-keyring' key ID: 0x%x\n", newring);
> > +		printf("'my-keyring' context:\n\t%s\n", r_con);
> > +	}
> > +
> > +	return 0;
> > +}
> > diff --git a/tests/keys/test b/tests/keys/test
> > new file mode 100755
> > index 0000000..4916e7c
> > --- /dev/null
> > +++ b/tests/keys/test
> > @@ -0,0 +1,98 @@
> > +#!/usr/bin/perl
> > +use Test::More;
> > +
> > +BEGIN {
> > +    $basedir = $0;
> > +    $basedir =~ s|(.*)/[^/]*|$1|;
> > +
> > +    $test_count = 13;
> > +
> > +    # 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 = " ";
> > +    }
> > +
> > +    # From kernel 5.3 request_key() requires additional check of
> > key { link }
> > +    $kvercur = `uname -r`;
> > +    chomp($kvercur);
> > +    $kverminstream = "5.3";
> > +    $test_link_53  = 0;
> > +
> > +    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
> > +    if ( $result >= 0 ) {
> > +        $test_link_53 = 1;
> > +    }
> > +
> > +    plan tests => $test_count;
> > +}
> > +
> > +############ Test keyctl #############
> > +print "Test key class permissions\n";
> > +$result = system "runcon -t test_key_t $basedir/keyctl $v";
> > +ok( $result eq 0 );
> > +
> > +# Deny process { setkeycreate }
> > +$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 3 );
> > +
> > +# Deny key { create }
> > +$result = system "runcon -t test_key_no_create_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 3 );
> > +
> > +# Deny key { write }
> > +$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 5 );
> > +
> > +# Deny key { search }
> > +$result = system "runcon -t test_key_no_search_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 8 );
> > +
> > +# Deny key { view }
> > +$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 10 );
> > +
> > +# Deny key { read }
> > +$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 11 );
> > +
> > +# Deny key { link }
> > +$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v
> > 2>&1";
> > +if ($test_link_53) {
> > +    ok( $result >> 8 eq 8 );
> > +}
> > +else {
> > +    ok( $result >> 8 eq 13 );
> > +}
> > +
> > +# Deny key { setattr }
> > +$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 14 );
> > +
> > +########### Change keyring context ##############
> > +print "Change keyring context\n";
> > +$result = system
> > +"runcon -t test_key_t $basedir/keyctl_relabel $v
> > system_u:system_r:test_newcon_key_t:s0";
> > +ok( $result eq 0 );
> > +
> > +############ Test key_socket #############
> > +print "Test key_socket class\n";
> > +$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
> > +ok( $result eq 0 );
> > +
> > +# Deny capability { net_admin } - EPERM
> > +$result =
> > +  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock
> > $v 2>&1";
> > +ok( $result >> 8 eq 1 );
> > +
> > +# Deny key_socket { create } - EACCES
> > +$result =
> > +  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v
> > 2>&1";
> > +ok( $result >> 8 eq 13 );
> > +
> > +exit;
> >
Stephen Smalley Sept. 16, 2019, 7:11 p.m. UTC | #3
On 9/16/19 2:55 PM, Richard Haines wrote:
> On Mon, 2019-09-16 at 13:58 -0400, Stephen Smalley wrote:
>> On 9/9/19 9:17 AM, Richard Haines wrote:
>>> Test all permissions associated with the key and key_socket
>>> classes.
>>>
>>> Note that kernel 5.3 commit keys: Fix request_key() lack of Link
>>> perm
>>> check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
>>> added an additional check for link perm on request_key(). The tests
>>> will support earlier kernels.
>>
>> I'm not sure why you coupled key and key_socket together; they don't
>> have anything to do with each other, and were introduced in very
>> different kernel and probably refpolicy releases.  I would recommend
>> splitting them.  SECCLASS_KEY and its permission checks were
>> introduced
>> in Linux v2.6.18; SECCLASS_KEY_SOCKET was part of the original
>> SELinux
>> merge for Linux 2.6.0.
> 
> I'll split them.
> 
>>
>> You only appear to be testing self access, not permission checks
>> between
>> a process and a keyring created by another process in a different
>> security context.
> 
> Okay I'll add these tests
>>
>> 1 test fails for me,
>> keys/test ................... Failed KEYCTL_SESSION_TO_PARENT:
>> Operation
>> not permitted
>> keys/test ................... 1/13
>> #   Failed test at keys/test line 38.
>> # Looks like you failed 1 test of 13.
>> keys/test ................... Dubious, test returned 1 (wstat 256,
>> 0x100)
> 
> You must have systems that don't like my patches - I can't get this
> fail. Using Fedora 30 and also Rawhide from a few weeks ago.

I'll have to look into it further, but it was on stock F30.

> I don't know if this is of any interest (It works on Rawhide with
> kernel from [1]):
> 
> I've been building 'key' tests to add the new permissions defined in
> kernel-next [1].
> To test these with new policy supporting the new perms + old policy
> that does not, I added the kernel test patch below.
> This patch handles security_key_permission() passing a single
> permission, as checking the current keys code I only see it passing a
> single permission at a time.
> 
> I've also an sepol patch + selinux-testsuite tests
> 
> [1]
> https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/security/selinux?h=next-20190904&id=1f96e0f129eb2bea15a00c154eee8b85aa181d1a

Yes, that's probably worth submitting for real.  Be sure to include 
David Howells on the distribution for it. I wouldn't assume that only a 
single permission can ever be passed unless key_permission() itself 
asserts that invariant.

> 
> 
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 901cc052f..9c8f90648 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6502,7 +6502,7 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   {
>   	struct key *key;
>   	struct key_security_struct *ksec;
> -	unsigned oldstyle_perm;
> +	unsigned int key_perm = 0;
>   	u32 sid;
>   
>   	/* if no specific permissions are requested, we skip the
> @@ -6511,18 +6511,57 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   	if (perm == 0)
>   		return 0;
>   
> -	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ |
> KEY_NEED_WRITE |
> -				KEY_NEED_SEARCH | KEY_NEED_LINK);
> -	if (perm & KEY_NEED_SETSEC)
> -		oldstyle_perm |= OLD_KEY_NEED_SETATTR;
> -	if (perm & KEY_NEED_INVAL)
> -		oldstyle_perm |= KEY_NEED_SEARCH;
> -	if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
> -		oldstyle_perm |= KEY_NEED_WRITE;
> -	if (perm & KEY_NEED_JOIN)
> -		oldstyle_perm |= KEY_NEED_SEARCH;
> -	if (perm & KEY_NEED_CLEAR)
> -		oldstyle_perm |= KEY_NEED_WRITE;
> +	/* selinux_key_permission() is called with only one permission
> set. */
> +	switch (perm) {
> +	case KEY_NEED_VIEW:
> +		key_perm = KEY__VIEW;
> +		break;
> +	case KEY_NEED_READ:
> +		key_perm = KEY__READ;
> +		break;
> +	case KEY_NEED_WRITE:
> +		key_perm = KEY__WRITE;
> +		break;
> +	case KEY_NEED_SEARCH:
> +		key_perm = KEY__SEARCH;
> +		break;
> +	case KEY_NEED_LINK:
> +		key_perm = KEY__LINK;
> +		break;
> +	case KEY_NEED_SETSEC: /* Keep this as "setattr" in policy */
> +		key_perm = KEY__SETATTR;
> +		break;
> +	case KEY_NEED_INVAL:
> +		key_perm = KEY__INVAL;
> +		break;
> +	case KEY_NEED_REVOKE:
> +		key_perm = KEY__REVOKE;
> +		break;
> +	case KEY_NEED_JOIN:
> +		key_perm = KEY__JOIN;
> +		break;
> +	case KEY_NEED_CLEAR:
> +		key_perm = KEY__CLEAR;
> +		break;
> +	}
> +
> +	/* If old policy, then reset new perms to orig. */
> +	if (!selinux_policycap_key_perms()) {
> +		switch (perm) {
> +		case KEY_NEED_INVAL:
> +			key_perm = KEY__SEARCH;
> +			break;
> +		case KEY_NEED_REVOKE:
> +			key_perm = KEY__WRITE;
> +			break;
> +		case KEY_NEED_JOIN:
> +			key_perm = KEY__SEARCH;
> +			break;
> +		case KEY_NEED_CLEAR:
> +			key_perm = KEY__WRITE;
> +			break;
> +		}
> +	}
>   
>   	sid = cred_sid(cred);
>   
> @@ -6530,7 +6569,7 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   	ksec = key->security;
>   
>   	return avc_has_perm(&selinux_state,
> -			    sid, ksec->sid, SECCLASS_KEY,
> oldstyle_perm, NULL);
> +			    sid, ksec->sid, SECCLASS_KEY, key_perm,
> NULL);
>   }
>   
>   static int selinux_key_getsecurity(struct key *key, char **_buffer)
> @@ -6555,7 +6594,7 @@ static int selinux_watch_key(struct key *key)
>   	u32 sid = current_sid();
>   
>   	return avc_has_perm(&selinux_state,
> -			    sid, ksec->sid, SECCLASS_KEY,
> KEY_NEED_VIEW, NULL);
> +			    sid, ksec->sid, SECCLASS_KEY, KEY__VIEW,
> NULL);
>   }
>   #endif
>   #endif
> diff --git a/security/selinux/include/classmap.h
> b/security/selinux/include/classmap.h
> index 201f7e588..a51ab9bd9 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -158,7 +158,7 @@ struct security_class_mapping secclass_map[] = {
>   	  { "send", "recv", "relabelto", "forward_in", "forward_out",
> NULL } },
>   	{ "key",
>   	  { "view", "read", "write", "search", "link", "setattr",
> "create",
> -	    NULL } },
> +	    "inval", "revoke", "join", "clear", NULL } },
>   	{ "dccp_socket",
>   	  { COMMON_SOCK_PERMS,
>   	    "node_bind", "name_connect", NULL } },
> diff --git a/security/selinux/include/security.h
> b/security/selinux/include/security.h
> index 111121281..a248eef75 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -78,6 +78,7 @@ enum {
>   	POLICYDB_CAPABILITY_ALWAYSNETWORK,
>   	POLICYDB_CAPABILITY_CGROUPSECLABEL,
>   	POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
> +	POLICYDB_CAPABILITY_KEYPERMS,
>   	__POLICYDB_CAPABILITY_MAX
>   };
>   #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
> @@ -177,6 +178,13 @@ static inline bool
> selinux_policycap_nnp_nosuid_transition(void)
>   	return state-
>> policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
>   }
>   
> +static inline bool selinux_policycap_key_perms(void)
> +{
> +	struct selinux_state *state = &selinux_state;
> +
> +	return state->policycap[POLICYDB_CAPABILITY_KEYPERMS];
> +}
> +
>   int security_mls_enabled(struct selinux_state *state);
>   int security_load_policy(struct selinux_state *state,
>   			 void *data, size_t len);
> diff --git a/security/selinux/ss/services.c
> b/security/selinux/ss/services.c
> index d61563a36..eb3949fc8 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -73,7 +73,8 @@ const char
> *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
>   	"extended_socket_class",
>   	"always_check_network",
>   	"cgroup_seclabel",
> -	"nnp_nosuid_transition"
> +	"nnp_nosuid_transition",
> +	"key_perms"
>   };
>   
>   static struct selinux_ss selinux_ss;
> 
> 
> 
>>
>>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>>> ---
>>>    README.md                   |   3 +-
>>>    defconfig                   |   8 ++
>>>    policy/Makefile             |   4 +
>>>    policy/test_keys.te         | 161 ++++++++++++++++++++++++
>>>    tests/Makefile              |   4 +
>>>    tests/keys/.gitignore       |   3 +
>>>    tests/keys/Makefile         |   8 ++
>>>    tests/keys/key_sock.c       |  67 ++++++++++
>>>    tests/keys/keyctl.c         | 241
>>> ++++++++++++++++++++++++++++++++++++
>>>    tests/keys/keyctl_relabel.c |  93 ++++++++++++++
>>>    tests/keys/test             |  98 +++++++++++++++
>>>    11 files changed, 689 insertions(+), 1 deletion(-)
>>>    create mode 100644 policy/test_keys.te
>>>    create mode 100644 tests/keys/.gitignore
>>>    create mode 100644 tests/keys/Makefile
>>>    create mode 100644 tests/keys/key_sock.c
>>>    create mode 100644 tests/keys/keyctl.c
>>>    create mode 100644 tests/keys/keyctl_relabel.c
>>>    create mode 100755 tests/keys/test
>>>
>>> diff --git a/README.md b/README.md
>>> index 26784f8..fe72a91 100644
>>> --- a/README.md
>>> +++ b/README.md
>>> @@ -65,7 +65,8 @@ following command:
>>>    		netlabel_tools \
>>>    		iptables \
>>>    		lksctp-tools-devel \
>>> -		attr
>>> +		attr \
>>> +		keyutils-libs-devel
>>>    
>>>    The testsuite requires a pre-existing base policy configuration
>>> of SELinux,
>>>    using either the old example policy or the reference policy as
>>> the baseline.
>>> diff --git a/defconfig b/defconfig
>>> index d7f0ea5..c00e291 100644
>>> --- a/defconfig
>>> +++ b/defconfig
>>> @@ -62,3 +62,11 @@ CONFIG_ANDROID_BINDER_IPC=y
>>>    # This will configure the Dynamically Allocated Binder Devices
>>> added
>>>    # to 5.0+ kernels:
>>>    CONFIG_ANDROID_BINDERFS=y
>>> +
>>> +# Key implementations.
>>> +# These are enabled to test the key and key_socket controls in
>>> +# tests/keys; they are not required for SELinux operation itself.
>>> +CONFIG_KEYS=y
>>> +CONFIG_KEYS_COMPAT=y
>>> +CONFIG_KEY_DH_OPERATIONS=y
>>> +CONFIG_NET_KEY=m
>>> diff --git a/policy/Makefile b/policy/Makefile
>>> index 305b572..9258a93 100644
>>> --- a/policy/Makefile
>>> +++ b/policy/Makefile
>>> @@ -71,6 +71,10 @@ ifeq ($(shell grep -q
>>> corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
>>>    TARGETS += test_sctp.te
>>>    endif
>>>    
>>> +ifeq ($(shell grep -q key_socket
>>> $(POLDEV)/include/support/all_perms.spt && echo true),true)
>>> +TARGETS += test_keys.te
>>> +endif
>>> +
>>>    ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
>>>    TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te,
>>> $(TARGETS))
>>>    endif
>>> diff --git a/policy/test_keys.te b/policy/test_keys.te
>>> new file mode 100644
>>> index 0000000..9c65ec8
>>> --- /dev/null
>>> +++ b/policy/test_keys.te
>>> @@ -0,0 +1,161 @@
>>> +#
>>> +################# KEY selinux-testsuite policy module
>>> ######################
>>> +#
>>> +attribute keydomain;
>>> +
>>> +#
>>> +############################## Define Macro
>>> ################################
>>> +#
>>> +# Do not use domain_type() macro as it has allow 'key { link
>>> search }'
>>> +# in base module so 'allow domain self:key ~{ link search };' will
>>> not work
>>> +# here. Add these instead to allow key perms to be controlled by
>>> this module:
>>> +#
>>> +define(`key_domain_type',`
>>> +	allow $1 proc_t:dir { search };
>>> +	allow $1 proc_t:lnk_file { read };
>>> +	allow $1 self:dir { search };
>>> +	allow $1 self:file { open read write };
>>> +')
>>> +
>>> +#
>>> +####################### Main key class tests #####################
>>> +#
>>> +type test_key_t;
>>> +key_domain_type(test_key_t)
>>> +unconfined_runs_test(test_key_t)
>>> +typeattribute test_key_t testdomain;
>>> +typeattribute test_key_t keydomain;
>>> +
>>> +allow test_key_t self:process { setkeycreate };
>>> +allow test_key_t self:key { create write search read view link
>>> setattr };
>>> +
>>> +# Set new context on a keyring:
>>> +type test_newcon_key_t;
>>> +key_domain_type(test_newcon_key_t)
>>> +unconfined_runs_test(test_newcon_key_t)
>>> +typeattribute test_newcon_key_t testdomain;
>>> +typeattribute test_newcon_key_t keydomain;
>>> +
>>> +allow test_key_t test_newcon_key_t:key { create write search view
>>> };
>>> +
>>> +################# Deny process { setkeycreate }
>>> #######################
>>> +type test_no_setkeycreate_t;
>>> +key_domain_type(test_no_setkeycreate_t)
>>> +unconfined_runs_test(test_no_setkeycreate_t)
>>> +typeattribute test_no_setkeycreate_t testdomain;
>>> +typeattribute test_no_setkeycreate_t keydomain;
>>> +
>>> +###################### Deny key { create }
>>> ###########################
>>> +type test_key_no_create_t;
>>> +key_domain_type(test_key_no_create_t)
>>> +unconfined_runs_test(test_key_no_create_t)
>>> +typeattribute test_key_no_create_t testdomain;
>>> +typeattribute test_key_no_create_t keydomain;
>>> +
>>> +allow test_key_no_create_t self:process { setkeycreate };
>>> +allow test_key_no_create_t self:key { write search read view link
>>> setattr };
>>> +
>>> +###################### Deny key { write }
>>> ###########################
>>> +type test_key_no_write_t;
>>> +key_domain_type(test_key_no_write_t)
>>> +unconfined_runs_test(test_key_no_write_t)
>>> +typeattribute test_key_no_write_t testdomain;
>>> +typeattribute test_key_no_write_t keydomain;
>>> +
>>> +allow test_key_no_write_t self:process { setkeycreate };
>>> +allow test_key_no_write_t self:key { create search read view link
>>> setattr };
>>> +
>>> +###################### Deny key { search }
>>> ###########################
>>> +type test_key_no_search_t;
>>> +key_domain_type(test_key_no_search_t)
>>> +unconfined_runs_test(test_key_no_search_t)
>>> +typeattribute test_key_no_search_t testdomain;
>>> +typeattribute test_key_no_search_t keydomain;
>>> +
>>> +allow test_key_no_search_t self:process { setkeycreate };
>>> +allow test_key_no_search_t self:key { create write read view link
>>> setattr };
>>> +
>>> +###################### Deny key { view }
>>> ###########################
>>> +type test_key_no_view_t;
>>> +key_domain_type(test_key_no_view_t)
>>> +unconfined_runs_test(test_key_no_view_t)
>>> +typeattribute test_key_no_view_t testdomain;
>>> +typeattribute test_key_no_view_t keydomain;
>>> +
>>> +allow test_key_no_view_t self:process { setkeycreate };
>>> +allow test_key_no_view_t self:key { create write search read link
>>> setattr };
>>> +
>>> +###################### Deny key { read }
>>> ###########################
>>> +type test_key_no_read_t;
>>> +key_domain_type(test_key_no_read_t)
>>> +unconfined_runs_test(test_key_no_read_t)
>>> +typeattribute test_key_no_read_t testdomain;
>>> +typeattribute test_key_no_read_t keydomain;
>>> +
>>> +allow test_key_no_read_t self:process { setkeycreate };
>>> +allow test_key_no_read_t self:key { create write search view link
>>> setattr };
>>> +
>>> +###################### Deny key { link }
>>> ###########################
>>> +type test_key_no_link_t;
>>> +key_domain_type(test_key_no_link_t)
>>> +unconfined_runs_test(test_key_no_link_t)
>>> +typeattribute test_key_no_link_t testdomain;
>>> +typeattribute test_key_no_link_t keydomain;
>>> +
>>> +allow test_key_no_link_t self:process { setkeycreate };
>>> +allow test_key_no_link_t self:key { create write search read view
>>> setattr };
>>> +
>>> +###################### Deny key { setattr }
>>> ###########################
>>> +type test_key_no_setattr_t;
>>> +key_domain_type(test_key_no_setattr_t)
>>> +unconfined_runs_test(test_key_no_setattr_t)
>>> +typeattribute test_key_no_setattr_t testdomain;
>>> +typeattribute test_key_no_setattr_t keydomain;
>>> +
>>> +allow test_key_no_setattr_t self:process { setkeycreate };
>>> +allow test_key_no_setattr_t self:key { create write search read
>>> view link };
>>> +
>>> +#
>>> +######################## Test key_socket class
>>> ###########################
>>> +#
>>> +type test_key_sock_t;
>>> +domain_type(test_key_sock_t)
>>> +unconfined_runs_test(test_key_sock_t)
>>> +typeattribute test_key_sock_t testdomain;
>>> +typeattribute test_key_sock_t keydomain;
>>> +
>>> +# key_socket rules:
>>> +allow test_key_sock_t self:rawip_socket { create };
>>> +allow test_key_sock_t self:capability { net_admin };
>>> +allow test_key_sock_t self:key_socket { create write };
>>> +# For CONFIG_NET_KEY=m
>>> +allow test_key_sock_t kernel_t:system { module_request };
>>> +
>>> +################## Deny capability { net_admin }
>>> ##########################
>>> +type test_key_sock_no_net_admin_t;
>>> +domain_type(test_key_sock_no_net_admin_t)
>>> +unconfined_runs_test(test_key_sock_no_net_admin_t)
>>> +typeattribute test_key_sock_no_net_admin_t testdomain;
>>> +typeattribute test_key_sock_no_net_admin_t keydomain;
>>> +
>>> +# key_socket rules:
>>> +allow test_key_sock_no_net_admin_t self:rawip_socket { create };
>>> +allow test_key_sock_no_net_admin_t self:key_socket { create write
>>> };
>>> +
>>> +####################### Deny key_socket { create }
>>> ##########################
>>> +type test_key_sock_no_create_t;
>>> +domain_type(test_key_sock_no_create_t)
>>> +unconfined_runs_test(test_key_sock_no_create_t)
>>> +typeattribute test_key_sock_no_create_t testdomain;
>>> +typeattribute test_key_sock_no_create_t keydomain;
>>> +
>>> +# key_socket rules:
>>> +allow test_key_sock_no_create_t self:rawip_socket { create };
>>> +allow test_key_sock_no_create_t self:capability { net_admin };
>>> +allow test_key_sock_no_create_t self:key_socket { write };
>>> +
>>> +#
>>> +########### Allow these domains to be entered from sysadm domain
>>> ############
>>> +#
>>> +miscfiles_domain_entry_test_files(keydomain)
>>> +userdom_sysadm_entry_spec_domtrans_to(keydomain)
>>> diff --git a/tests/Makefile b/tests/Makefile
>>> index 63aa325..d1dbf38 100644
>>> --- a/tests/Makefile
>>> +++ b/tests/Makefile
>>> @@ -42,6 +42,10 @@ ifeq ($(shell grep -q binder
>>> $(POLDEV)/include/support/all_perms.spt && test -e
>>>    SUBDIRS += binder
>>>    endif
>>>    
>>> +ifeq ($(shell grep -q key_socket
>>> $(POLDEV)/include/support/all_perms.spt && test -e
>>> $(INCLUDEDIR)/keyutils.h && echo true),true)
>>> +SUBDIRS += keys
>>> +endif
>>> +
>>>    ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST="
>>> infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
>>>    SUBDIRS += infiniband_endport
>>>    endif
>>> diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
>>> new file mode 100644
>>> index 0000000..4a0a48d
>>> --- /dev/null
>>> +++ b/tests/keys/.gitignore
>>> @@ -0,0 +1,3 @@
>>> +keyctl
>>> +keyctl_relabel
>>> +key_sock
>>> diff --git a/tests/keys/Makefile b/tests/keys/Makefile
>>> new file mode 100644
>>> index 0000000..3a00df5
>>> --- /dev/null
>>> +++ b/tests/keys/Makefile
>>> @@ -0,0 +1,8 @@
>>> +TARGETS = keyctl key_sock keyctl_relabel
>>> +
>>> +LDLIBS += -lselinux -lkeyutils
>>> +
>>> +all: $(TARGETS)
>>> +
>>> +clean:
>>> +	rm -f $(TARGETS)
>>> diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
>>> new file mode 100644
>>> index 0000000..8ac1f45
>>> --- /dev/null
>>> +++ b/tests/keys/key_sock.c
>>> @@ -0,0 +1,67 @@
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <unistd.h>
>>> +#include <errno.h>
>>> +#include <stdbool.h>
>>> +#include <sys/socket.h>
>>> +#include <linux/pfkeyv2.h>
>>> +#include <selinux/selinux.h>
>>> +
>>> +static void 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[])
>>> +{
>>> +	char *context;
>>> +	int opt, sock, result;
>>> +	bool verbose = false;
>>> +
>>> +	while ((opt = getopt(argc, argv, "v")) != -1) {
>>> +		switch (opt) {
>>> +		case 'v':
>>> +			verbose = true;
>>> +			break;
>>> +		default:
>>> +			usage(argv[0]);
>>> +		}
>>> +	}
>>> +
>>> +	result = getcon(&context);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain process context\n");
>>> +		exit(-1);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Process context: %s\n", context);
>>> +
>>> +	sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
>>> +	if (sock < 0) {
>>> +		fprintf(stderr, "Failed to open PF_KEY socket: %s\n",
>>> +			strerror(errno));
>>> +		exit(errno);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Opened PF_KEY socket\n");
>>> +
>>> +	/* Write nothing to socket for test, expect EMSGSIZE error */
>>> +	result = write(sock, NULL, 0);
>>> +	if (result < 0 && errno == EMSGSIZE) {
>>> +		result = 0;
>>> +		if (verbose)
>>> +			printf("Written to PF_KEY socket\n");
>>> +	} else if (result < 0 && errno != EMSGSIZE) {
>>> +		result = -1;
>>> +		fprintf(stderr, "Failed write to PF_KEY socket: %s\n",
>>> +			strerror(errno));
>>> +	}
>>> +
>>> +	close(sock);
>>> +	return result;
>>> +}
>>> diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
>>> new file mode 100644
>>> index 0000000..6d85be7
>>> --- /dev/null
>>> +++ b/tests/keys/keyctl.c
>>> @@ -0,0 +1,241 @@
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <unistd.h>
>>> +#include <errno.h>
>>> +#include <stdbool.h>
>>> +#include <keyutils.h>
>>> +#include <selinux/selinux.h>
>>> +
>>> +/* This is used as the payload for each add_key() */
>>> +static const char payload[] =
>>> +	" -----BEGIN PUBLIC KEY-----
>>> \nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\
>>> nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\n
>>> Ww4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4
>>> aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
>>> +
>>> +static void 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, result;
>>> +	char *context, *keycreate_con;
>>> +	char r_con[256];
>>> +	bool verbose = false;
>>> +	key_serial_t retrieved, search, link, compute, newring,
>>> +		     private, prime, base, test_key;
>>> +	struct keyctl_dh_params params;
>>> +
>>> +	while ((opt = getopt(argc, argv, "v")) != -1) {
>>> +		switch (opt) {
>>> +		case 'v':
>>> +			verbose = true;
>>> +			break;
>>> +		default:
>>> +			usage(argv[0]);
>>> +		}
>>> +	}
>>> +
>>> +	result = getcon(&context);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain process context\n");
>>> +		exit(1);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Process context: %s\n", context);
>>> +
>>> +	result = getkeycreatecon(&keycreate_con);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain keycreate
>>> context\n");
>>> +		exit(2);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Current keycreate context: %s\n",
>>> keycreate_con);
>>> +	free(keycreate_con);
>>> +
>>> +	/* Set context requires process { setkeycreate } and key {
>>> create } */
>>> +	result = setkeycreatecon(context);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
>>> +			strerror(errno));
>>> +		exit(3);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Set keycreate context: %s\n", context);
>>> +	free(context);
>>> +
>>> +	result = getkeycreatecon(&keycreate_con);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain keycreate
>>> context\n");
>>> +		exit(4);
>>> +	}
>>> +	if (verbose)
>>> +		printf("New keycreate context: %s\n", keycreate_con);
>>> +	free(keycreate_con);
>>> +
>>> +	/*
>>> +	 * Add three keys as these will be required by the
>>> +	 * keyctl(KEYCTL_DH_COMPUTE, ..) function.
>>> +	 * These require key { create write } permissions.
>>> +	 */
>>> +	private = add_key("user", "private", payload, strlen(payload),
>>> +			  KEY_SPEC_PROCESS_KEYRING);
>>> +	if (private < 0) {
>>> +		fprintf(stderr, "Failed add_key(private): %s\n",
>>> +			strerror(errno));
>>> +		exit(5);
>>> +	}
>>> +
>>> +	prime = add_key("user", "prime", payload, strlen(payload),
>>> +			KEY_SPEC_PROCESS_KEYRING);
>>> +	if (prime < 0) {
>>> +		fprintf(stderr, "Failed add_key(prime): %s\n",
>>> +			strerror(errno));
>>> +		exit(6);
>>> +	}
>>> +
>>> +	base = add_key("user", "base", payload, strlen(payload),
>>> +		       KEY_SPEC_PROCESS_KEYRING);
>>> +	if (base < 0) {
>>> +		fprintf(stderr, "Failed add_key(base): %s\n",
>>> +			strerror(errno));
>>> +		exit(7);
>>> +	}
>>> +
>>> +	if (verbose) {
>>> +		printf("Private key ID: 0x%x\n", private);
>>> +		printf("Prime key ID:   0x%x\n", prime);
>>> +		printf("Base key ID:    0x%x\n", base);
>>> +	}
>>> +
>>> +	/* Requires key { search }. From kernel 5.3 also requires {
>>> link } */
>>> +	retrieved = request_key("user", "private", NULL,
>>> +				KEY_SPEC_PROCESS_KEYRING);
>>> +	if (retrieved < 0) {
>>> +		fprintf(stderr, "Failed to request 'private' key:
>>> %s\n",
>>> +			strerror(errno));
>>> +		exit(8);
>>> +	}
>>> +
>>> +	/* Requires key { search } */
>>> +	search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING,
>>> "user",
>>> +			"base", 0);
>>> +	if (search < 0) {
>>> +		fprintf(stderr, "Failed to find 'base' key: %s\n",
>>> +			strerror(errno));
>>> +		exit(9);
>>> +	}
>>> +
>>> +	/* Requires key { view } */
>>> +	result = keyctl(KEYCTL_GET_SECURITY, search, r_con,
>>> sizeof(r_con));
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain key context: %s\n",
>>> +			strerror(errno));
>>> +		exit(10);
>>> +	}
>>> +
>>> +	if (verbose) {
>>> +		printf("Requested 'private' key ID: 0x%x\n",
>>> retrieved);
>>> +		printf("Searched 'base' key ID:     0x%x\n", search);
>>> +		printf("Searched 'base' key context:\n\t%s\n", r_con);
>>> +	}
>>> +
>>> +	/* Compute DH key, only obtain the length for test, not the
>>> key. */
>>> +	params.priv = private;
>>> +	params.prime = prime;
>>> +	params.base = base;
>>> +
>>> +	/* Requires key { create read write } */
>>> +	compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
>>> +	if (compute < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
>>> +			strerror(errno));
>>> +		exit(11);
>>> +	}
>>> +	if (verbose)
>>> +		printf("KEYCTL_DH_COMPUTE key ID size: %d\n", compute);
>>> +
>>> +	/* To test key { link }, need to generate a new keyring ID
>>> first */
>>> +	newring = add_key("keyring", "my-keyring", NULL, 0,
>>> +			  KEY_SPEC_THREAD_KEYRING);
>>> +	if (newring < 0) {
>>> +		fprintf(stderr, "Failed to add new keyring: %s\n",
>>> +			strerror(errno));
>>> +		exit(12);
>>> +	}
>>> +	if (verbose)
>>> +		printf("New keyring ID: 0x%x\n", newring);
>>> +
>>> +	/* Requires key { write link } */
>>> +	link = keyctl(KEYCTL_LINK, base, newring);
>>> +	if (link < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
>>> +			strerror(errno));
>>> +		exit(13);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Link key ID:    0x%x\n", newring);
>>> +
>>> +	/* Requires key { setattr } */
>>> +	link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL, NULL);
>>> +	if (link < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING: %s\n",
>>> +			strerror(errno));
>>> +		exit(14);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Restricted keyring\n");
>>> +
>>> +	/* Requires key { search } from 5.X key { inval } */
>>> +	test_key = keyctl(KEYCTL_INVALIDATE, private);
>>> +	if (test_key < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_INVALIDATE(private):
>>> %s\n",
>>> +			strerror(errno));
>>> +		exit(15);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Invalidated 'private' key\n");
>>> +
>>> +	/* Requires key { write setattr } from 5.X key { revoke } */
>>> +	test_key = keyctl(KEYCTL_REVOKE, prime);
>>> +	if (test_key < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_REVOKE(prime): %s\n",
>>> +			strerror(errno));
>>> +		exit(16);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Revoked 'prime' key\n");
>>> +
>>> +	/* Requires key { write } from 5.X key { clear } */
>>> +	test_key = keyctl(KEYCTL_CLEAR, newring);
>>> +	if (test_key < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_CLEAR(newring): %s\n",
>>> +			strerror(errno));
>>> +		exit(17);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Cleared 'newring' keyring\n");
>>> +
>>> +	/* To test key { join }, need to join session first */
>>> +	test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
>>> +	if (test_key < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_JOIN_SESSION_KEYRING,:
>>> %s\n",
>>> +			strerror(errno));
>>> +		exit(18);
>>> +	}
>>> +	/* Requires key { link } from 5.X key { join } */
>>> +	test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
>>> +	if (test_key < 0) {
>>> +		fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT:
>>> %s\n",
>>> +			strerror(errno));
>>> +		exit(19);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Joined session to parent\n");
>>> +
>>> +	return 0;
>>> +}
>>> diff --git a/tests/keys/keyctl_relabel.c
>>> b/tests/keys/keyctl_relabel.c
>>> new file mode 100644
>>> index 0000000..0276c7a
>>> --- /dev/null
>>> +++ b/tests/keys/keyctl_relabel.c
>>> @@ -0,0 +1,93 @@
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <unistd.h>
>>> +#include <errno.h>
>>> +#include <stdbool.h>
>>> +#include <keyutils.h>
>>> +#include <selinux/selinux.h>
>>> +
>>> +static void usage(char *progname)
>>> +{
>>> +	fprintf(stderr,
>>> +		"usage:  %s [-v] newcon\n"
>>> +		"Where:\n\t"
>>> +		"-v      Print information.\n\t"
>>> +		"newcon  New keyring context.\n", progname);
>>> +	exit(-1);
>>> +}
>>> +
>>> +int main(int argc, char *argv[])
>>> +{
>>> +	int opt, result;
>>> +	char *context, *keycreate_con;
>>> +	char r_con[256];
>>> +	bool verbose = false;
>>> +	key_serial_t newring;
>>> +
>>> +	while ((opt = getopt(argc, argv, "v")) != -1) {
>>> +		switch (opt) {
>>> +		case 'v':
>>> +			verbose = true;
>>> +			break;
>>> +		default:
>>> +			usage(argv[0]);
>>> +		}
>>> +	}
>>> +
>>> +	if (optind >= argc)
>>> +		usage(argv[0]);
>>> +
>>> +	result = getcon(&context);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain process context\n");
>>> +		exit(1);
>>> +	}
>>> +	if (verbose)
>>> +		printf("Process context: %s\n", context);
>>> +	free(context);
>>> +
>>> +	result = setkeycreatecon(argv[optind]);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
>>> +			strerror(errno));
>>> +		exit(2);
>>> +	}
>>> +
>>> +	result = getkeycreatecon(&keycreate_con);
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain keycreate
>>> context\n");
>>> +		exit(3);
>>> +	}
>>> +	if (verbose)
>>> +		printf("New keycreate context: %s\n", keycreate_con);
>>> +	free(keycreate_con);
>>> +
>>> +	newring = add_key("keyring", "my-keyring", NULL, 0,
>>> +			  KEY_SPEC_THREAD_KEYRING);
>>> +	if (newring < 0) {
>>> +		fprintf(stderr, "Failed to add new keyring: %s\n",
>>> +			strerror(errno));
>>> +		exit(4);
>>> +	}
>>> +
>>> +	result = keyctl(KEYCTL_GET_SECURITY, newring, r_con,
>>> sizeof(r_con));
>>> +	if (result < 0) {
>>> +		fprintf(stderr, "Failed to obtain key context: %s\n",
>>> +			strerror(errno));
>>> +		exit(5);
>>> +	}
>>> +
>>> +	if (strcmp(argv[optind], r_con)) {
>>> +		fprintf(stderr, "Relabel error - expected: %s got:
>>> %s\n",
>>> +			argv[optind], r_con);
>>> +		exit(6);
>>> +	}
>>> +
>>> +	if (verbose) {
>>> +		printf("'my-keyring' key ID: 0x%x\n", newring);
>>> +		printf("'my-keyring' context:\n\t%s\n", r_con);
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> diff --git a/tests/keys/test b/tests/keys/test
>>> new file mode 100755
>>> index 0000000..4916e7c
>>> --- /dev/null
>>> +++ b/tests/keys/test
>>> @@ -0,0 +1,98 @@
>>> +#!/usr/bin/perl
>>> +use Test::More;
>>> +
>>> +BEGIN {
>>> +    $basedir = $0;
>>> +    $basedir =~ s|(.*)/[^/]*|$1|;
>>> +
>>> +    $test_count = 13;
>>> +
>>> +    # 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 = " ";
>>> +    }
>>> +
>>> +    # From kernel 5.3 request_key() requires additional check of
>>> key { link }
>>> +    $kvercur = `uname -r`;
>>> +    chomp($kvercur);
>>> +    $kverminstream = "5.3";
>>> +    $test_link_53  = 0;
>>> +
>>> +    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
>>> +    if ( $result >= 0 ) {
>>> +        $test_link_53 = 1;
>>> +    }
>>> +
>>> +    plan tests => $test_count;
>>> +}
>>> +
>>> +############ Test keyctl #############
>>> +print "Test key class permissions\n";
>>> +$result = system "runcon -t test_key_t $basedir/keyctl $v";
>>> +ok( $result eq 0 );
>>> +
>>> +# Deny process { setkeycreate }
>>> +$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl
>>> $v 2>&1";
>>> +ok( $result >> 8 eq 3 );
>>> +
>>> +# Deny key { create }
>>> +$result = system "runcon -t test_key_no_create_t $basedir/keyctl
>>> $v 2>&1";
>>> +ok( $result >> 8 eq 3 );
>>> +
>>> +# Deny key { write }
>>> +$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v
>>> 2>&1";
>>> +ok( $result >> 8 eq 5 );
>>> +
>>> +# Deny key { search }
>>> +$result = system "runcon -t test_key_no_search_t $basedir/keyctl
>>> $v 2>&1";
>>> +ok( $result >> 8 eq 8 );
>>> +
>>> +# Deny key { view }
>>> +$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v
>>> 2>&1";
>>> +ok( $result >> 8 eq 10 );
>>> +
>>> +# Deny key { read }
>>> +$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v
>>> 2>&1";
>>> +ok( $result >> 8 eq 11 );
>>> +
>>> +# Deny key { link }
>>> +$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v
>>> 2>&1";
>>> +if ($test_link_53) {
>>> +    ok( $result >> 8 eq 8 );
>>> +}
>>> +else {
>>> +    ok( $result >> 8 eq 13 );
>>> +}
>>> +
>>> +# Deny key { setattr }
>>> +$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl
>>> $v 2>&1";
>>> +ok( $result >> 8 eq 14 );
>>> +
>>> +########### Change keyring context ##############
>>> +print "Change keyring context\n";
>>> +$result = system
>>> +"runcon -t test_key_t $basedir/keyctl_relabel $v
>>> system_u:system_r:test_newcon_key_t:s0";
>>> +ok( $result eq 0 );
>>> +
>>> +############ Test key_socket #############
>>> +print "Test key_socket class\n";
>>> +$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
>>> +ok( $result eq 0 );
>>> +
>>> +# Deny capability { net_admin } - EPERM
>>> +$result =
>>> +  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock
>>> $v 2>&1";
>>> +ok( $result >> 8 eq 1 );
>>> +
>>> +# Deny key_socket { create } - EACCES
>>> +$result =
>>> +  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v
>>> 2>&1";
>>> +ok( $result >> 8 eq 13 );
>>> +
>>> +exit;
>>>
>
Richard Haines Sept. 16, 2019, 7:23 p.m. UTC | #4
On Mon, 2019-09-16 at 15:11 -0400, Stephen Smalley wrote:
> On 9/16/19 2:55 PM, Richard Haines wrote:
> > On Mon, 2019-09-16 at 13:58 -0400, Stephen Smalley wrote:
> > > On 9/9/19 9:17 AM, Richard Haines wrote:
> > > > Test all permissions associated with the key and key_socket
> > > > classes.
> > > > 
> > > > Note that kernel 5.3 commit keys: Fix request_key() lack of
> > > > Link
> > > > perm
> > > > check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
> > > > added an additional check for link perm on request_key(). The
> > > > tests
> > > > will support earlier kernels.
> > > 
> > > I'm not sure why you coupled key and key_socket together; they
> > > don't
> > > have anything to do with each other, and were introduced in very
> > > different kernel and probably refpolicy releases.  I would
> > > recommend
> > > splitting them.  SECCLASS_KEY and its permission checks were
> > > introduced
> > > in Linux v2.6.18; SECCLASS_KEY_SOCKET was part of the original
> > > SELinux
> > > merge for Linux 2.6.0.
> > 
> > I'll split them.
> > 
> > > You only appear to be testing self access, not permission checks
> > > between
> > > a process and a keyring created by another process in a different
> > > security context.
> > 
> > Okay I'll add these tests
> > > 1 test fails for me,
> > > keys/test ................... Failed KEYCTL_SESSION_TO_PARENT:
> > > Operation
> > > not permitted
> > > keys/test ................... 1/13
> > > #   Failed test at keys/test line 38.
> > > # Looks like you failed 1 test of 13.
> > > keys/test ................... Dubious, test returned 1 (wstat
> > > 256,
> > > 0x100)
> > 
> > You must have systems that don't like my patches - I can't get this
> > fail. Using Fedora 30 and also Rawhide from a few weeks ago.
> 
> I'll have to look into it further, but it was on stock F30.
> 
> > I don't know if this is of any interest (It works on Rawhide with
> > kernel from [1]):
> > 
> > I've been building 'key' tests to add the new permissions defined
> > in
> > kernel-next [1].
> > To test these with new policy supporting the new perms + old policy
> > that does not, I added the kernel test patch below.
> > This patch handles security_key_permission() passing a single
> > permission, as checking the current keys code I only see it passing
> > a
> > single permission at a time.
> > 
> > I've also an sepol patch + selinux-testsuite tests
> > 
> > [1]
> > https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/security/selinux?h=next-20190904&id=1f96e0f129eb2bea15a00c154eee8b85aa181d1a
> 
> Yes, that's probably worth submitting for real.  Be sure to include 
> David Howells on the distribution for it. I wouldn't assume that only
> a 
> single permission can ever be passed unless key_permission() itself 
> asserts that invariant.
> 
That's okay as I've also tested the patch below that handles multiple
permissions from keys:
Any view on what is best !!

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 901cc052f..78413277c 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6502,7 +6502,8 @@ static int selinux_key_permission(key_ref_t
key_ref,
 {
 	struct key *key;
 	struct key_security_struct *ksec;
-	unsigned oldstyle_perm;
+	unsigned int key_perm = 0, switch_perm = 0;
+	int x = KEY_NEED_ALL, bit = 1;
 	u32 sid;
 
 	/* if no specific permissions are requested, we skip the
@@ -6511,18 +6512,67 @@ static int selinux_key_permission(key_ref_t
key_ref,
 	if (perm == 0)
 		return 0;
 
-	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ |
KEY_NEED_WRITE |
-				KEY_NEED_SEARCH | KEY_NEED_LINK);
-	if (perm & KEY_NEED_SETSEC)
-		oldstyle_perm |= OLD_KEY_NEED_SETATTR;
-	if (perm & KEY_NEED_INVAL)
-		oldstyle_perm |= KEY_NEED_SEARCH;
-	if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
-		oldstyle_perm |= KEY_NEED_WRITE;
-	if (perm & KEY_NEED_JOIN)
-		oldstyle_perm |= KEY_NEED_SEARCH;
-	if (perm & KEY_NEED_CLEAR)
-		oldstyle_perm |= KEY_NEED_WRITE;
+	/*
+	 * selinux_key_permission() is called with only one permission
set.
+	 * However this will handle multiple bits set.
+	 */
+	while (x) {
+		switch_perm = bit & perm;
+		switch (switch_perm) {
+		case KEY_NEED_VIEW:
+			key_perm |= KEY__VIEW;
+			break;
+		case KEY_NEED_READ:
+			key_perm |= KEY__READ;
+			break;
+		case KEY_NEED_WRITE:
+			key_perm |= KEY__WRITE;
+			break;
+		case KEY_NEED_SEARCH:
+			key_perm |= KEY__SEARCH;
+			break;
+		case KEY_NEED_LINK:
+			key_perm |= KEY__LINK;
+			break;
+		case KEY_NEED_SETSEC: /* Keep this as "setattr" in
policy */
+			key_perm |= KEY__SETATTR;
+			break;
+		case KEY_NEED_INVAL:
+			key_perm |= KEY__INVAL;
+			break;
+		case KEY_NEED_REVOKE:
+			key_perm |= KEY__REVOKE;
+			break;
+		case KEY_NEED_JOIN:
+			key_perm |= KEY__JOIN;
+			break;
+		case KEY_NEED_CLEAR:
+			key_perm |= KEY__CLEAR;
+			break;
+		}
+		bit <<= 1;
+		x >>= 1;
+	}
+
+	/* If old policy, then reset new perms to orig. */
+	if (!selinux_policycap_key_perms()) {
+		if (perm & KEY_NEED_INVAL) {
+			key_perm &= ~KEY__INVAL;
+			key_perm |= KEY__SEARCH;
+		}
+		if (perm & KEY_NEED_REVOKE && !(perm &
OLD_KEY_NEED_SETATTR)) {
+			key_perm &= ~KEY__REVOKE;
+			key_perm |= KEY__WRITE;
+		}
+		if (perm & KEY_NEED_JOIN) {
+			key_perm &= ~KEY__JOIN;
+			key_perm |= KEY__SEARCH;
+		}
+		if (perm & KEY_NEED_CLEAR) {
+			key_perm &= ~KEY__CLEAR;
+			key_perm |= KEY__WRITE;
+		}
+	}
 
 	sid = cred_sid(cred);
 
@@ -6530,7 +6580,7 @@ static int selinux_key_permission(key_ref_t
key_ref,
 	ksec = key->security;
 
 	return avc_has_perm(&selinux_state,
-			    sid, ksec->sid, SECCLASS_KEY,
oldstyle_perm, NULL);
+			    sid, ksec->sid, SECCLASS_KEY, key_perm,
NULL);
 }
 
 static int selinux_key_getsecurity(struct key *key, char **_buffer)
@@ -6555,7 +6605,7 @@ static int selinux_watch_key(struct key *key)
 	u32 sid = current_sid();
 
 	return avc_has_perm(&selinux_state,
-			    sid, ksec->sid, SECCLASS_KEY,
KEY_NEED_VIEW, NULL);
+			    sid, ksec->sid, SECCLASS_KEY, KEY__VIEW,
NULL);
 }
 #endif
 #endif
diff --git a/security/selinux/include/classmap.h
b/security/selinux/include/classmap.h
index 201f7e588..a51ab9bd9 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -158,7 +158,7 @@ struct security_class_mapping secclass_map[] = {
 	  { "send", "recv", "relabelto", "forward_in", "forward_out",
NULL } },
 	{ "key",
 	  { "view", "read", "write", "search", "link", "setattr",
"create",
-	    NULL } },
+	    "inval", "revoke", "join", "clear", NULL } },
 	{ "dccp_socket",
 	  { COMMON_SOCK_PERMS,
 	    "node_bind", "name_connect", NULL } },
diff --git a/security/selinux/include/security.h
b/security/selinux/include/security.h
index 111121281..a248eef75 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -78,6 +78,7 @@ enum {
 	POLICYDB_CAPABILITY_ALWAYSNETWORK,
 	POLICYDB_CAPABILITY_CGROUPSECLABEL,
 	POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
+	POLICYDB_CAPABILITY_KEYPERMS,
 	__POLICYDB_CAPABILITY_MAX
 };
 #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
@@ -177,6 +178,13 @@ static inline bool
selinux_policycap_nnp_nosuid_transition(void)
 	return state-
>policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
 }
 
+static inline bool selinux_policycap_key_perms(void)
+{
+	struct selinux_state *state = &selinux_state;
+
+	return state->policycap[POLICYDB_CAPABILITY_KEYPERMS];
+}
+
 int security_mls_enabled(struct selinux_state *state);
 int security_load_policy(struct selinux_state *state,
 			 void *data, size_t len);
diff --git a/security/selinux/ss/services.c
b/security/selinux/ss/services.c
index d61563a36..eb3949fc8 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -73,7 +73,8 @@ const char
*selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
 	"extended_socket_class",
 	"always_check_network",
 	"cgroup_seclabel",
-	"nnp_nosuid_transition"
+	"nnp_nosuid_transition",
+	"key_perms"
 };
 
 static struct selinux_ss selinux_ss;
Stephen Smalley Sept. 16, 2019, 7:39 p.m. UTC | #5
On 9/16/19 3:23 PM, Richard Haines wrote:
> On Mon, 2019-09-16 at 15:11 -0400, Stephen Smalley wrote:
>> On 9/16/19 2:55 PM, Richard Haines wrote:
>>> On Mon, 2019-09-16 at 13:58 -0400, Stephen Smalley wrote:
>>>> On 9/9/19 9:17 AM, Richard Haines wrote:
>>>>> Test all permissions associated with the key and key_socket
>>>>> classes.
>>>>>
>>>>> Note that kernel 5.3 commit keys: Fix request_key() lack of
>>>>> Link
>>>>> perm
>>>>> check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
>>>>> added an additional check for link perm on request_key(). The
>>>>> tests
>>>>> will support earlier kernels.
>>>>
>>>> I'm not sure why you coupled key and key_socket together; they
>>>> don't
>>>> have anything to do with each other, and were introduced in very
>>>> different kernel and probably refpolicy releases.  I would
>>>> recommend
>>>> splitting them.  SECCLASS_KEY and its permission checks were
>>>> introduced
>>>> in Linux v2.6.18; SECCLASS_KEY_SOCKET was part of the original
>>>> SELinux
>>>> merge for Linux 2.6.0.
>>>
>>> I'll split them.
>>>
>>>> You only appear to be testing self access, not permission checks
>>>> between
>>>> a process and a keyring created by another process in a different
>>>> security context.
>>>
>>> Okay I'll add these tests
>>>> 1 test fails for me,
>>>> keys/test ................... Failed KEYCTL_SESSION_TO_PARENT:
>>>> Operation
>>>> not permitted
>>>> keys/test ................... 1/13
>>>> #   Failed test at keys/test line 38.
>>>> # Looks like you failed 1 test of 13.
>>>> keys/test ................... Dubious, test returned 1 (wstat
>>>> 256,
>>>> 0x100)
>>>
>>> You must have systems that don't like my patches - I can't get this
>>> fail. Using Fedora 30 and also Rawhide from a few weeks ago.
>>
>> I'll have to look into it further, but it was on stock F30.
>>
>>> I don't know if this is of any interest (It works on Rawhide with
>>> kernel from [1]):
>>>
>>> I've been building 'key' tests to add the new permissions defined
>>> in
>>> kernel-next [1].
>>> To test these with new policy supporting the new perms + old policy
>>> that does not, I added the kernel test patch below.
>>> This patch handles security_key_permission() passing a single
>>> permission, as checking the current keys code I only see it passing
>>> a
>>> single permission at a time.
>>>
>>> I've also an sepol patch + selinux-testsuite tests
>>>
>>> [1]
>>> https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/security/selinux?h=next-20190904&id=1f96e0f129eb2bea15a00c154eee8b85aa181d1a
>>
>> Yes, that's probably worth submitting for real.  Be sure to include
>> David Howells on the distribution for it. I wouldn't assume that only
>> a
>> single permission can ever be passed unless key_permission() itself
>> asserts that invariant.
>>
> That's okay as I've also tested the patch below that handles multiple
> permissions from keys:
> Any view on what is best !!

Unless key_task_permission() prohibits passing multiple permissions or 
David Howells tells you it will never happen, I'd support it in the 
security hook.  selinux_inode_permission() and selinux_ipc_permission() 
support it for their checks.  I don't think I'd do the work of mapping 
to the new permissions unless the policycap is set.

> 
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 901cc052f..78413277c 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6502,7 +6502,8 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   {
>   	struct key *key;
>   	struct key_security_struct *ksec;
> -	unsigned oldstyle_perm;
> +	unsigned int key_perm = 0, switch_perm = 0;
> +	int x = KEY_NEED_ALL, bit = 1;
>   	u32 sid;
>   
>   	/* if no specific permissions are requested, we skip the
> @@ -6511,18 +6512,67 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   	if (perm == 0)
>   		return 0;
>   
> -	oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ |
> KEY_NEED_WRITE |
> -				KEY_NEED_SEARCH | KEY_NEED_LINK);
> -	if (perm & KEY_NEED_SETSEC)
> -		oldstyle_perm |= OLD_KEY_NEED_SETATTR;
> -	if (perm & KEY_NEED_INVAL)
> -		oldstyle_perm |= KEY_NEED_SEARCH;
> -	if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
> -		oldstyle_perm |= KEY_NEED_WRITE;
> -	if (perm & KEY_NEED_JOIN)
> -		oldstyle_perm |= KEY_NEED_SEARCH;
> -	if (perm & KEY_NEED_CLEAR)
> -		oldstyle_perm |= KEY_NEED_WRITE;
> +	/*
> +	 * selinux_key_permission() is called with only one permission
> set.
> +	 * However this will handle multiple bits set.
> +	 */
> +	while (x) {
> +		switch_perm = bit & perm;
> +		switch (switch_perm) {
> +		case KEY_NEED_VIEW:
> +			key_perm |= KEY__VIEW;
> +			break;
> +		case KEY_NEED_READ:
> +			key_perm |= KEY__READ;
> +			break;
> +		case KEY_NEED_WRITE:
> +			key_perm |= KEY__WRITE;
> +			break;
> +		case KEY_NEED_SEARCH:
> +			key_perm |= KEY__SEARCH;
> +			break;
> +		case KEY_NEED_LINK:
> +			key_perm |= KEY__LINK;
> +			break;
> +		case KEY_NEED_SETSEC: /* Keep this as "setattr" in
> policy */
> +			key_perm |= KEY__SETATTR;
> +			break;
> +		case KEY_NEED_INVAL:
> +			key_perm |= KEY__INVAL;
> +			break;
> +		case KEY_NEED_REVOKE:
> +			key_perm |= KEY__REVOKE;
> +			break;
> +		case KEY_NEED_JOIN:
> +			key_perm |= KEY__JOIN;
> +			break;
> +		case KEY_NEED_CLEAR:
> +			key_perm |= KEY__CLEAR;
> +			break;
> +		}
> +		bit <<= 1;
> +		x >>= 1;
> +	}
> +
> +	/* If old policy, then reset new perms to orig. */
> +	if (!selinux_policycap_key_perms()) {
> +		if (perm & KEY_NEED_INVAL) {
> +			key_perm &= ~KEY__INVAL;
> +			key_perm |= KEY__SEARCH;
> +		}
> +		if (perm & KEY_NEED_REVOKE && !(perm &
> OLD_KEY_NEED_SETATTR)) {
> +			key_perm &= ~KEY__REVOKE;
> +			key_perm |= KEY__WRITE;
> +		}
> +		if (perm & KEY_NEED_JOIN) {
> +			key_perm &= ~KEY__JOIN;
> +			key_perm |= KEY__SEARCH;
> +		}
> +		if (perm & KEY_NEED_CLEAR) {
> +			key_perm &= ~KEY__CLEAR;
> +			key_perm |= KEY__WRITE;
> +		}
> +	}
>   
>   	sid = cred_sid(cred);
>   
> @@ -6530,7 +6580,7 @@ static int selinux_key_permission(key_ref_t
> key_ref,
>   	ksec = key->security;
>   
>   	return avc_has_perm(&selinux_state,
> -			    sid, ksec->sid, SECCLASS_KEY,
> oldstyle_perm, NULL);
> +			    sid, ksec->sid, SECCLASS_KEY, key_perm,
> NULL);
>   }
>   
>   static int selinux_key_getsecurity(struct key *key, char **_buffer)
> @@ -6555,7 +6605,7 @@ static int selinux_watch_key(struct key *key)
>   	u32 sid = current_sid();
>   
>   	return avc_has_perm(&selinux_state,
> -			    sid, ksec->sid, SECCLASS_KEY,
> KEY_NEED_VIEW, NULL);
> +			    sid, ksec->sid, SECCLASS_KEY, KEY__VIEW,
> NULL);
>   }
>   #endif
>   #endif
> diff --git a/security/selinux/include/classmap.h
> b/security/selinux/include/classmap.h
> index 201f7e588..a51ab9bd9 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -158,7 +158,7 @@ struct security_class_mapping secclass_map[] = {
>   	  { "send", "recv", "relabelto", "forward_in", "forward_out",
> NULL } },
>   	{ "key",
>   	  { "view", "read", "write", "search", "link", "setattr",
> "create",
> -	    NULL } },
> +	    "inval", "revoke", "join", "clear", NULL } },
>   	{ "dccp_socket",
>   	  { COMMON_SOCK_PERMS,
>   	    "node_bind", "name_connect", NULL } },
> diff --git a/security/selinux/include/security.h
> b/security/selinux/include/security.h
> index 111121281..a248eef75 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -78,6 +78,7 @@ enum {
>   	POLICYDB_CAPABILITY_ALWAYSNETWORK,
>   	POLICYDB_CAPABILITY_CGROUPSECLABEL,
>   	POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
> +	POLICYDB_CAPABILITY_KEYPERMS,
>   	__POLICYDB_CAPABILITY_MAX
>   };
>   #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
> @@ -177,6 +178,13 @@ static inline bool
> selinux_policycap_nnp_nosuid_transition(void)
>   	return state-
>> policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
>   }
>   
> +static inline bool selinux_policycap_key_perms(void)
> +{
> +	struct selinux_state *state = &selinux_state;
> +
> +	return state->policycap[POLICYDB_CAPABILITY_KEYPERMS];
> +}
> +
>   int security_mls_enabled(struct selinux_state *state);
>   int security_load_policy(struct selinux_state *state,
>   			 void *data, size_t len);
> diff --git a/security/selinux/ss/services.c
> b/security/selinux/ss/services.c
> index d61563a36..eb3949fc8 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -73,7 +73,8 @@ const char
> *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
>   	"extended_socket_class",
>   	"always_check_network",
>   	"cgroup_seclabel",
> -	"nnp_nosuid_transition"
> +	"nnp_nosuid_transition",
> +	"key_perms"
>   };
>   
>   static struct selinux_ss selinux_ss;
> 
>
Ondrej Mosnacek Sept. 17, 2019, 7:04 a.m. UTC | #6
On Mon, Sep 9, 2019 at 3:17 PM Richard Haines
<richard_c_haines@btinternet.com> wrote:
> Test all permissions associated with the key and key_socket classes.
>
> Note that kernel 5.3 commit keys: Fix request_key() lack of Link perm
> check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
> added an additional check for link perm on request_key(). The tests
> will support earlier kernels.
>
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>  README.md                   |   3 +-
>  defconfig                   |   8 ++
>  policy/Makefile             |   4 +
>  policy/test_keys.te         | 161 ++++++++++++++++++++++++
>  tests/Makefile              |   4 +
>  tests/keys/.gitignore       |   3 +
>  tests/keys/Makefile         |   8 ++
>  tests/keys/key_sock.c       |  67 ++++++++++
>  tests/keys/keyctl.c         | 241 ++++++++++++++++++++++++++++++++++++
>  tests/keys/keyctl_relabel.c |  93 ++++++++++++++
>  tests/keys/test             |  98 +++++++++++++++
>  11 files changed, 689 insertions(+), 1 deletion(-)
>  create mode 100644 policy/test_keys.te
>  create mode 100644 tests/keys/.gitignore
>  create mode 100644 tests/keys/Makefile
>  create mode 100644 tests/keys/key_sock.c
>  create mode 100644 tests/keys/keyctl.c
>  create mode 100644 tests/keys/keyctl_relabel.c
>  create mode 100755 tests/keys/test
>
> diff --git a/README.md b/README.md
> index 26784f8..fe72a91 100644
> --- a/README.md
> +++ b/README.md
> @@ -65,7 +65,8 @@ following command:
>                 netlabel_tools \
>                 iptables \
>                 lksctp-tools-devel \
> -               attr
> +               attr \
> +               keyutils-libs-devel
>
>  The testsuite requires a pre-existing base policy configuration of SELinux,
>  using either the old example policy or the reference policy as the baseline.
> diff --git a/defconfig b/defconfig
> index d7f0ea5..c00e291 100644
> --- a/defconfig
> +++ b/defconfig
> @@ -62,3 +62,11 @@ CONFIG_ANDROID_BINDER_IPC=y
>  # This will configure the Dynamically Allocated Binder Devices added
>  # to 5.0+ kernels:
>  CONFIG_ANDROID_BINDERFS=y
> +
> +# Key implementations.
> +# These are enabled to test the key and key_socket controls in
> +# tests/keys; they are not required for SELinux operation itself.
> +CONFIG_KEYS=y
> +CONFIG_KEYS_COMPAT=y
> +CONFIG_KEY_DH_OPERATIONS=y
> +CONFIG_NET_KEY=m
> diff --git a/policy/Makefile b/policy/Makefile
> index 305b572..9258a93 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -71,6 +71,10 @@ ifeq ($(shell grep -q corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
>  TARGETS += test_sctp.te
>  endif
>
> +ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && echo true),true)

You might want to include a grep for KEYCTL_RESTRICT_KEYRING in
keyutils.h to make sure a recent enough keyutils library is installed.
For example, on RHEL-8 there is some old keyutils version (1.5.10+)
and the test fails to build there.

> +TARGETS += test_keys.te
> +endif
> +
>  ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
>  TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
>  endif
> diff --git a/policy/test_keys.te b/policy/test_keys.te
> new file mode 100644
> index 0000000..9c65ec8
> --- /dev/null
> +++ b/policy/test_keys.te
> @@ -0,0 +1,161 @@
> +#
> +################# KEY selinux-testsuite policy module ######################
> +#
> +attribute keydomain;
> +
> +#
> +############################## Define Macro ################################
> +#
> +# Do not use domain_type() macro as it has allow 'key { link search }'
> +# in base module so 'allow domain self:key ~{ link search };' will not work
> +# here. Add these instead to allow key perms to be controlled by this module:
> +#
> +define(`key_domain_type',`
> +       allow $1 proc_t:dir { search };
> +       allow $1 proc_t:lnk_file { read };
> +       allow $1 self:dir { search };
> +       allow $1 self:file { open read write };
> +')
> +
> +#
> +####################### Main key class tests #####################
> +#
> +type test_key_t;
> +key_domain_type(test_key_t)
> +unconfined_runs_test(test_key_t)
> +typeattribute test_key_t testdomain;
> +typeattribute test_key_t keydomain;
> +
> +allow test_key_t self:process { setkeycreate };
> +allow test_key_t self:key { create write search read view link setattr };
> +
> +# Set new context on a keyring:
> +type test_newcon_key_t;
> +key_domain_type(test_newcon_key_t)
> +unconfined_runs_test(test_newcon_key_t)
> +typeattribute test_newcon_key_t testdomain;
> +typeattribute test_newcon_key_t keydomain;
> +
> +allow test_key_t test_newcon_key_t:key { create write search view };
> +
> +################# Deny process { setkeycreate } #######################
> +type test_no_setkeycreate_t;
> +key_domain_type(test_no_setkeycreate_t)
> +unconfined_runs_test(test_no_setkeycreate_t)
> +typeattribute test_no_setkeycreate_t testdomain;
> +typeattribute test_no_setkeycreate_t keydomain;
> +
> +###################### Deny key { create } ###########################
> +type test_key_no_create_t;
> +key_domain_type(test_key_no_create_t)
> +unconfined_runs_test(test_key_no_create_t)
> +typeattribute test_key_no_create_t testdomain;
> +typeattribute test_key_no_create_t keydomain;
> +
> +allow test_key_no_create_t self:process { setkeycreate };
> +allow test_key_no_create_t self:key { write search read view link setattr };
> +
> +###################### Deny key { write } ###########################
> +type test_key_no_write_t;
> +key_domain_type(test_key_no_write_t)
> +unconfined_runs_test(test_key_no_write_t)
> +typeattribute test_key_no_write_t testdomain;
> +typeattribute test_key_no_write_t keydomain;
> +
> +allow test_key_no_write_t self:process { setkeycreate };
> +allow test_key_no_write_t self:key { create search read view link setattr };
> +
> +###################### Deny key { search } ###########################
> +type test_key_no_search_t;
> +key_domain_type(test_key_no_search_t)
> +unconfined_runs_test(test_key_no_search_t)
> +typeattribute test_key_no_search_t testdomain;
> +typeattribute test_key_no_search_t keydomain;
> +
> +allow test_key_no_search_t self:process { setkeycreate };
> +allow test_key_no_search_t self:key { create write read view link setattr };
> +
> +###################### Deny key { view } ###########################
> +type test_key_no_view_t;
> +key_domain_type(test_key_no_view_t)
> +unconfined_runs_test(test_key_no_view_t)
> +typeattribute test_key_no_view_t testdomain;
> +typeattribute test_key_no_view_t keydomain;
> +
> +allow test_key_no_view_t self:process { setkeycreate };
> +allow test_key_no_view_t self:key { create write search read link setattr };
> +
> +###################### Deny key { read } ###########################
> +type test_key_no_read_t;
> +key_domain_type(test_key_no_read_t)
> +unconfined_runs_test(test_key_no_read_t)
> +typeattribute test_key_no_read_t testdomain;
> +typeattribute test_key_no_read_t keydomain;
> +
> +allow test_key_no_read_t self:process { setkeycreate };
> +allow test_key_no_read_t self:key { create write search view link setattr };
> +
> +###################### Deny key { link } ###########################
> +type test_key_no_link_t;
> +key_domain_type(test_key_no_link_t)
> +unconfined_runs_test(test_key_no_link_t)
> +typeattribute test_key_no_link_t testdomain;
> +typeattribute test_key_no_link_t keydomain;
> +
> +allow test_key_no_link_t self:process { setkeycreate };
> +allow test_key_no_link_t self:key { create write search read view setattr };
> +
> +###################### Deny key { setattr } ###########################
> +type test_key_no_setattr_t;
> +key_domain_type(test_key_no_setattr_t)
> +unconfined_runs_test(test_key_no_setattr_t)
> +typeattribute test_key_no_setattr_t testdomain;
> +typeattribute test_key_no_setattr_t keydomain;
> +
> +allow test_key_no_setattr_t self:process { setkeycreate };
> +allow test_key_no_setattr_t self:key { create write search read view link };
> +
> +#
> +######################## Test key_socket class ###########################
> +#
> +type test_key_sock_t;
> +domain_type(test_key_sock_t)
> +unconfined_runs_test(test_key_sock_t)
> +typeattribute test_key_sock_t testdomain;
> +typeattribute test_key_sock_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_t self:rawip_socket { create };
> +allow test_key_sock_t self:capability { net_admin };
> +allow test_key_sock_t self:key_socket { create write };
> +# For CONFIG_NET_KEY=m
> +allow test_key_sock_t kernel_t:system { module_request };
> +
> +################## Deny capability { net_admin } ##########################
> +type test_key_sock_no_net_admin_t;
> +domain_type(test_key_sock_no_net_admin_t)
> +unconfined_runs_test(test_key_sock_no_net_admin_t)
> +typeattribute test_key_sock_no_net_admin_t testdomain;
> +typeattribute test_key_sock_no_net_admin_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_no_net_admin_t self:rawip_socket { create };
> +allow test_key_sock_no_net_admin_t self:key_socket { create write };
> +
> +####################### Deny key_socket { create } ##########################
> +type test_key_sock_no_create_t;
> +domain_type(test_key_sock_no_create_t)
> +unconfined_runs_test(test_key_sock_no_create_t)
> +typeattribute test_key_sock_no_create_t testdomain;
> +typeattribute test_key_sock_no_create_t keydomain;
> +
> +# key_socket rules:
> +allow test_key_sock_no_create_t self:rawip_socket { create };
> +allow test_key_sock_no_create_t self:capability { net_admin };
> +allow test_key_sock_no_create_t self:key_socket { write };
> +
> +#
> +########### Allow these domains to be entered from sysadm domain ############
> +#
> +miscfiles_domain_entry_test_files(keydomain)
> +userdom_sysadm_entry_spec_domtrans_to(keydomain)
> diff --git a/tests/Makefile b/tests/Makefile
> index 63aa325..d1dbf38 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -42,6 +42,10 @@ ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && test -e
>  SUBDIRS += binder
>  endif
>
> +ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && test -e $(INCLUDEDIR)/keyutils.h && echo true),true)
> +SUBDIRS += keys
> +endif
> +
>  ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
>  SUBDIRS += infiniband_endport
>  endif
> diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
> new file mode 100644
> index 0000000..4a0a48d
> --- /dev/null
> +++ b/tests/keys/.gitignore
> @@ -0,0 +1,3 @@
> +keyctl
> +keyctl_relabel
> +key_sock
> diff --git a/tests/keys/Makefile b/tests/keys/Makefile
> new file mode 100644
> index 0000000..3a00df5
> --- /dev/null
> +++ b/tests/keys/Makefile
> @@ -0,0 +1,8 @@
> +TARGETS = keyctl key_sock keyctl_relabel
> +
> +LDLIBS += -lselinux -lkeyutils
> +
> +all: $(TARGETS)
> +
> +clean:
> +       rm -f $(TARGETS)
> diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
> new file mode 100644
> index 0000000..8ac1f45
> --- /dev/null
> +++ b/tests/keys/key_sock.c
> @@ -0,0 +1,67 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <sys/socket.h>
> +#include <linux/pfkeyv2.h>
> +#include <selinux/selinux.h>
> +
> +static void 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[])
> +{
> +       char *context;
> +       int opt, sock, result;
> +       bool verbose = false;
> +
> +       while ((opt = getopt(argc, argv, "v")) != -1) {
> +               switch (opt) {
> +               case 'v':
> +                       verbose = true;
> +                       break;
> +               default:
> +                       usage(argv[0]);
> +               }
> +       }
> +
> +       result = getcon(&context);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain process context\n");
> +               exit(-1);
> +       }
> +       if (verbose)
> +               printf("Process context: %s\n", context);
> +
> +       sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
> +       if (sock < 0) {
> +               fprintf(stderr, "Failed to open PF_KEY socket: %s\n",
> +                       strerror(errno));
> +               exit(errno);
> +       }
> +       if (verbose)
> +               printf("Opened PF_KEY socket\n");
> +
> +       /* Write nothing to socket for test, expect EMSGSIZE error */
> +       result = write(sock, NULL, 0);
> +       if (result < 0 && errno == EMSGSIZE) {
> +               result = 0;
> +               if (verbose)
> +                       printf("Written to PF_KEY socket\n");
> +       } else if (result < 0 && errno != EMSGSIZE) {
> +               result = -1;
> +               fprintf(stderr, "Failed write to PF_KEY socket: %s\n",
> +                       strerror(errno));
> +       }
> +
> +       close(sock);
> +       return result;
> +}
> diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
> new file mode 100644
> index 0000000..6d85be7
> --- /dev/null
> +++ b/tests/keys/keyctl.c
> @@ -0,0 +1,241 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <keyutils.h>
> +#include <selinux/selinux.h>
> +
> +/* This is used as the payload for each add_key() */
> +static const char payload[] =
> +       " -----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\nWw4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
> +
> +static void 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, result;
> +       char *context, *keycreate_con;
> +       char r_con[256];
> +       bool verbose = false;
> +       key_serial_t retrieved, search, link, compute, newring,
> +                    private, prime, base, test_key;
> +       struct keyctl_dh_params params;
> +
> +       while ((opt = getopt(argc, argv, "v")) != -1) {
> +               switch (opt) {
> +               case 'v':
> +                       verbose = true;
> +                       break;
> +               default:
> +                       usage(argv[0]);
> +               }
> +       }
> +
> +       result = getcon(&context);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain process context\n");
> +               exit(1);
> +       }
> +       if (verbose)
> +               printf("Process context: %s\n", context);
> +
> +       result = getkeycreatecon(&keycreate_con);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain keycreate context\n");
> +               exit(2);
> +       }
> +       if (verbose)
> +               printf("Current keycreate context: %s\n", keycreate_con);
> +       free(keycreate_con);
> +
> +       /* Set context requires process { setkeycreate } and key { create } */
> +       result = setkeycreatecon(context);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> +                       strerror(errno));
> +               exit(3);
> +       }
> +       if (verbose)
> +               printf("Set keycreate context: %s\n", context);
> +       free(context);
> +
> +       result = getkeycreatecon(&keycreate_con);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain keycreate context\n");
> +               exit(4);
> +       }
> +       if (verbose)
> +               printf("New keycreate context: %s\n", keycreate_con);
> +       free(keycreate_con);
> +
> +       /*
> +        * Add three keys as these will be required by the
> +        * keyctl(KEYCTL_DH_COMPUTE, ..) function.
> +        * These require key { create write } permissions.
> +        */
> +       private = add_key("user", "private", payload, strlen(payload),
> +                         KEY_SPEC_PROCESS_KEYRING);
> +       if (private < 0) {
> +               fprintf(stderr, "Failed add_key(private): %s\n",
> +                       strerror(errno));
> +               exit(5);
> +       }
> +
> +       prime = add_key("user", "prime", payload, strlen(payload),
> +                       KEY_SPEC_PROCESS_KEYRING);
> +       if (prime < 0) {
> +               fprintf(stderr, "Failed add_key(prime): %s\n",
> +                       strerror(errno));
> +               exit(6);
> +       }
> +
> +       base = add_key("user", "base", payload, strlen(payload),
> +                      KEY_SPEC_PROCESS_KEYRING);
> +       if (base < 0) {
> +               fprintf(stderr, "Failed add_key(base): %s\n",
> +                       strerror(errno));
> +               exit(7);
> +       }
> +
> +       if (verbose) {
> +               printf("Private key ID: 0x%x\n", private);
> +               printf("Prime key ID:   0x%x\n", prime);
> +               printf("Base key ID:    0x%x\n", base);
> +       }
> +
> +       /* Requires key { search }. From kernel 5.3 also requires { link } */
> +       retrieved = request_key("user", "private", NULL,
> +                               KEY_SPEC_PROCESS_KEYRING);
> +       if (retrieved < 0) {
> +               fprintf(stderr, "Failed to request 'private' key: %s\n",
> +                       strerror(errno));
> +               exit(8);
> +       }
> +
> +       /* Requires key { search } */
> +       search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING, "user",
> +                       "base", 0);
> +       if (search < 0) {
> +               fprintf(stderr, "Failed to find 'base' key: %s\n",
> +                       strerror(errno));
> +               exit(9);
> +       }
> +
> +       /* Requires key { view } */
> +       result = keyctl(KEYCTL_GET_SECURITY, search, r_con, sizeof(r_con));
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain key context: %s\n",
> +                       strerror(errno));
> +               exit(10);
> +       }
> +
> +       if (verbose) {
> +               printf("Requested 'private' key ID: 0x%x\n", retrieved);
> +               printf("Searched 'base' key ID:     0x%x\n", search);
> +               printf("Searched 'base' key context:\n\t%s\n", r_con);
> +       }
> +
> +       /* Compute DH key, only obtain the length for test, not the key. */
> +       params.priv = private;
> +       params.prime = prime;
> +       params.base = base;
> +
> +       /* Requires key { create read write } */
> +       compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
> +       if (compute < 0) {
> +               fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
> +                       strerror(errno));
> +               exit(11);
> +       }
> +       if (verbose)
> +               printf("KEYCTL_DH_COMPUTE key ID size: %d\n", compute);
> +
> +       /* To test key { link }, need to generate a new keyring ID first */
> +       newring = add_key("keyring", "my-keyring", NULL, 0,
> +                         KEY_SPEC_THREAD_KEYRING);
> +       if (newring < 0) {
> +               fprintf(stderr, "Failed to add new keyring: %s\n",
> +                       strerror(errno));
> +               exit(12);
> +       }
> +       if (verbose)
> +               printf("New keyring ID: 0x%x\n", newring);
> +
> +       /* Requires key { write link } */
> +       link = keyctl(KEYCTL_LINK, base, newring);
> +       if (link < 0) {
> +               fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
> +                       strerror(errno));
> +               exit(13);
> +       }
> +       if (verbose)
> +               printf("Link key ID:    0x%x\n", newring);
> +
> +       /* Requires key { setattr } */
> +       link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL, NULL);
> +       if (link < 0) {
> +               fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING: %s\n",
> +                       strerror(errno));
> +               exit(14);
> +       }
> +       if (verbose)
> +               printf("Restricted keyring\n");
> +
> +       /* Requires key { search } from 5.X key { inval } */
> +       test_key = keyctl(KEYCTL_INVALIDATE, private);
> +       if (test_key < 0) {
> +               fprintf(stderr, "Failed KEYCTL_INVALIDATE(private): %s\n",
> +                       strerror(errno));
> +               exit(15);
> +       }
> +       if (verbose)
> +               printf("Invalidated 'private' key\n");
> +
> +       /* Requires key { write setattr } from 5.X key { revoke } */
> +       test_key = keyctl(KEYCTL_REVOKE, prime);
> +       if (test_key < 0) {
> +               fprintf(stderr, "Failed KEYCTL_REVOKE(prime): %s\n",
> +                       strerror(errno));
> +               exit(16);
> +       }
> +       if (verbose)
> +               printf("Revoked 'prime' key\n");
> +
> +       /* Requires key { write } from 5.X key { clear } */
> +       test_key = keyctl(KEYCTL_CLEAR, newring);
> +       if (test_key < 0) {
> +               fprintf(stderr, "Failed KEYCTL_CLEAR(newring): %s\n",
> +                       strerror(errno));
> +               exit(17);
> +       }
> +       if (verbose)
> +               printf("Cleared 'newring' keyring\n");
> +
> +       /* To test key { join }, need to join session first */
> +       test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
> +       if (test_key < 0) {
> +               fprintf(stderr, "Failed KEYCTL_JOIN_SESSION_KEYRING,: %s\n",
> +                       strerror(errno));
> +               exit(18);
> +       }
> +       /* Requires key { link } from 5.X key { join } */
> +       test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
> +       if (test_key < 0) {
> +               fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT: %s\n",
> +                       strerror(errno));
> +               exit(19);
> +       }
> +       if (verbose)
> +               printf("Joined session to parent\n");
> +
> +       return 0;
> +}
> diff --git a/tests/keys/keyctl_relabel.c b/tests/keys/keyctl_relabel.c
> new file mode 100644
> index 0000000..0276c7a
> --- /dev/null
> +++ b/tests/keys/keyctl_relabel.c
> @@ -0,0 +1,93 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <keyutils.h>
> +#include <selinux/selinux.h>
> +
> +static void usage(char *progname)
> +{
> +       fprintf(stderr,
> +               "usage:  %s [-v] newcon\n"
> +               "Where:\n\t"
> +               "-v      Print information.\n\t"
> +               "newcon  New keyring context.\n", progname);
> +       exit(-1);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +       int opt, result;
> +       char *context, *keycreate_con;
> +       char r_con[256];
> +       bool verbose = false;
> +       key_serial_t newring;
> +
> +       while ((opt = getopt(argc, argv, "v")) != -1) {
> +               switch (opt) {
> +               case 'v':
> +                       verbose = true;
> +                       break;
> +               default:
> +                       usage(argv[0]);
> +               }
> +       }
> +
> +       if (optind >= argc)
> +               usage(argv[0]);
> +
> +       result = getcon(&context);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain process context\n");
> +               exit(1);
> +       }
> +       if (verbose)
> +               printf("Process context: %s\n", context);
> +       free(context);
> +
> +       result = setkeycreatecon(argv[optind]);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> +                       strerror(errno));
> +               exit(2);
> +       }
> +
> +       result = getkeycreatecon(&keycreate_con);
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain keycreate context\n");
> +               exit(3);
> +       }
> +       if (verbose)
> +               printf("New keycreate context: %s\n", keycreate_con);
> +       free(keycreate_con);
> +
> +       newring = add_key("keyring", "my-keyring", NULL, 0,
> +                         KEY_SPEC_THREAD_KEYRING);
> +       if (newring < 0) {
> +               fprintf(stderr, "Failed to add new keyring: %s\n",
> +                       strerror(errno));
> +               exit(4);
> +       }
> +
> +       result = keyctl(KEYCTL_GET_SECURITY, newring, r_con, sizeof(r_con));
> +       if (result < 0) {
> +               fprintf(stderr, "Failed to obtain key context: %s\n",
> +                       strerror(errno));
> +               exit(5);
> +       }
> +
> +       if (strcmp(argv[optind], r_con)) {
> +               fprintf(stderr, "Relabel error - expected: %s got: %s\n",
> +                       argv[optind], r_con);
> +               exit(6);
> +       }
> +
> +       if (verbose) {
> +               printf("'my-keyring' key ID: 0x%x\n", newring);
> +               printf("'my-keyring' context:\n\t%s\n", r_con);
> +       }
> +
> +       return 0;
> +}
> diff --git a/tests/keys/test b/tests/keys/test
> new file mode 100755
> index 0000000..4916e7c
> --- /dev/null
> +++ b/tests/keys/test
> @@ -0,0 +1,98 @@
> +#!/usr/bin/perl
> +use Test::More;
> +
> +BEGIN {
> +    $basedir = $0;
> +    $basedir =~ s|(.*)/[^/]*|$1|;
> +
> +    $test_count = 13;
> +
> +    # 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 = " ";
> +    }
> +
> +    # From kernel 5.3 request_key() requires additional check of key { link }
> +    $kvercur = `uname -r`;
> +    chomp($kvercur);
> +    $kverminstream = "5.3";
> +    $test_link_53  = 0;
> +
> +    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
> +    if ( $result >= 0 ) {
> +        $test_link_53 = 1;
> +    }
> +
> +    plan tests => $test_count;
> +}
> +
> +############ Test keyctl #############
> +print "Test key class permissions\n";
> +$result = system "runcon -t test_key_t $basedir/keyctl $v";
> +ok( $result eq 0 );
> +
> +# Deny process { setkeycreate }
> +$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 3 );
> +
> +# Deny key { create }
> +$result = system "runcon -t test_key_no_create_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 3 );
> +
> +# Deny key { write }
> +$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 5 );
> +
> +# Deny key { search }
> +$result = system "runcon -t test_key_no_search_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# Deny key { view }
> +$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 10 );
> +
> +# Deny key { read }
> +$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 11 );
> +
> +# Deny key { link }
> +$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v 2>&1";
> +if ($test_link_53) {
> +    ok( $result >> 8 eq 8 );
> +}
> +else {
> +    ok( $result >> 8 eq 13 );
> +}
> +
> +# Deny key { setattr }
> +$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl $v 2>&1";
> +ok( $result >> 8 eq 14 );
> +
> +########### Change keyring context ##############
> +print "Change keyring context\n";
> +$result = system
> +"runcon -t test_key_t $basedir/keyctl_relabel $v system_u:system_r:test_newcon_key_t:s0";
> +ok( $result eq 0 );
> +
> +############ Test key_socket #############
> +print "Test key_socket class\n";
> +$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
> +ok( $result eq 0 );
> +
> +# Deny capability { net_admin } - EPERM
> +$result =
> +  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock $v 2>&1";
> +ok( $result >> 8 eq 1 );
> +
> +# Deny key_socket { create } - EACCES
> +$result =
> +  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v 2>&1";
> +ok( $result >> 8 eq 13 );
> +
> +exit;
> --
> 2.21.0
>

--
Ondrej Mosnacek <omosnace at redhat dot com>
Software Engineer, Security Technologies
Red Hat, Inc.
Richard Haines Sept. 19, 2019, 4:29 p.m. UTC | #7
On Tue, 2019-09-17 at 09:04 +0200, Ondrej Mosnacek wrote:
> On Mon, Sep 9, 2019 at 3:17 PM Richard Haines
> <richard_c_haines@btinternet.com> wrote:
> > Test all permissions associated with the key and key_socket
> > classes.
> > 
> > Note that kernel 5.3 commit keys: Fix request_key() lack of Link
> > perm
> > check on found key ("504b69eb3c95180bc59f1ae9096ad4b10bbbf254")
> > added an additional check for link perm on request_key(). The tests
> > will support earlier kernels.
> > 
> > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> > ---
> >  README.md                   |   3 +-
> >  defconfig                   |   8 ++
> >  policy/Makefile             |   4 +
> >  policy/test_keys.te         | 161 ++++++++++++++++++++++++
> >  tests/Makefile              |   4 +
> >  tests/keys/.gitignore       |   3 +
> >  tests/keys/Makefile         |   8 ++
> >  tests/keys/key_sock.c       |  67 ++++++++++
> >  tests/keys/keyctl.c         | 241
> > ++++++++++++++++++++++++++++++++++++
> >  tests/keys/keyctl_relabel.c |  93 ++++++++++++++
> >  tests/keys/test             |  98 +++++++++++++++
> >  11 files changed, 689 insertions(+), 1 deletion(-)
> >  create mode 100644 policy/test_keys.te
> >  create mode 100644 tests/keys/.gitignore
> >  create mode 100644 tests/keys/Makefile
> >  create mode 100644 tests/keys/key_sock.c
> >  create mode 100644 tests/keys/keyctl.c
> >  create mode 100644 tests/keys/keyctl_relabel.c
> >  create mode 100755 tests/keys/test
> > 
> > diff --git a/README.md b/README.md
> > index 26784f8..fe72a91 100644
> > --- a/README.md
> > +++ b/README.md
> > @@ -65,7 +65,8 @@ following command:
> >                 netlabel_tools \
> >                 iptables \
> >                 lksctp-tools-devel \
> > -               attr
> > +               attr \
> > +               keyutils-libs-devel
> > 
> >  The testsuite requires a pre-existing base policy configuration of
> > SELinux,
> >  using either the old example policy or the reference policy as the
> > baseline.
> > diff --git a/defconfig b/defconfig
> > index d7f0ea5..c00e291 100644
> > --- a/defconfig
> > +++ b/defconfig
> > @@ -62,3 +62,11 @@ CONFIG_ANDROID_BINDER_IPC=y
> >  # This will configure the Dynamically Allocated Binder Devices
> > added
> >  # to 5.0+ kernels:
> >  CONFIG_ANDROID_BINDERFS=y
> > +
> > +# Key implementations.
> > +# These are enabled to test the key and key_socket controls in
> > +# tests/keys; they are not required for SELinux operation itself.
> > +CONFIG_KEYS=y
> > +CONFIG_KEYS_COMPAT=y
> > +CONFIG_KEY_DH_OPERATIONS=y
> > +CONFIG_NET_KEY=m
> > diff --git a/policy/Makefile b/policy/Makefile
> > index 305b572..9258a93 100644
> > --- a/policy/Makefile
> > +++ b/policy/Makefile
> > @@ -71,6 +71,10 @@ ifeq ($(shell grep -q
> > corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
> >  TARGETS += test_sctp.te
> >  endif
> > 
> > +ifeq ($(shell grep -q key_socket
> > $(POLDEV)/include/support/all_perms.spt && echo true),true)
> 
> You might want to include a grep for KEYCTL_RESTRICT_KEYRING in
> keyutils.h to make sure a recent enough keyutils library is
> installed.
> For example, on RHEL-8 there is some old keyutils version (1.5.10+)
> and the test fails to build there.
> 
Thanks for the info. I'll remove the KEYCTL_RESTRICT_KEYRING function
as I use it only to check setattr permission. I can use
KEYCTL_SET_TIMEOUT instead.

> > +TARGETS += test_keys.te
> > +endif
> > +
> >  ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
> >  TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te,
> > $(TARGETS))
> >  endif
> > diff --git a/policy/test_keys.te b/policy/test_keys.te
> > new file mode 100644
> > index 0000000..9c65ec8
> > --- /dev/null
> > +++ b/policy/test_keys.te
> > @@ -0,0 +1,161 @@
> > +#
> > +################# KEY selinux-testsuite policy module
> > ######################
> > +#
> > +attribute keydomain;
> > +
> > +#
> > +############################## Define Macro
> > ################################
> > +#
> > +# Do not use domain_type() macro as it has allow 'key { link
> > search }'
> > +# in base module so 'allow domain self:key ~{ link search };' will
> > not work
> > +# here. Add these instead to allow key perms to be controlled by
> > this module:
> > +#
> > +define(`key_domain_type',`
> > +       allow $1 proc_t:dir { search };
> > +       allow $1 proc_t:lnk_file { read };
> > +       allow $1 self:dir { search };
> > +       allow $1 self:file { open read write };
> > +')
> > +
> > +#
> > +####################### Main key class tests #####################
> > +#
> > +type test_key_t;
> > +key_domain_type(test_key_t)
> > +unconfined_runs_test(test_key_t)
> > +typeattribute test_key_t testdomain;
> > +typeattribute test_key_t keydomain;
> > +
> > +allow test_key_t self:process { setkeycreate };
> > +allow test_key_t self:key { create write search read view link
> > setattr };
> > +
> > +# Set new context on a keyring:
> > +type test_newcon_key_t;
> > +key_domain_type(test_newcon_key_t)
> > +unconfined_runs_test(test_newcon_key_t)
> > +typeattribute test_newcon_key_t testdomain;
> > +typeattribute test_newcon_key_t keydomain;
> > +
> > +allow test_key_t test_newcon_key_t:key { create write search view
> > };
> > +
> > +################# Deny process { setkeycreate }
> > #######################
> > +type test_no_setkeycreate_t;
> > +key_domain_type(test_no_setkeycreate_t)
> > +unconfined_runs_test(test_no_setkeycreate_t)
> > +typeattribute test_no_setkeycreate_t testdomain;
> > +typeattribute test_no_setkeycreate_t keydomain;
> > +
> > +###################### Deny key { create }
> > ###########################
> > +type test_key_no_create_t;
> > +key_domain_type(test_key_no_create_t)
> > +unconfined_runs_test(test_key_no_create_t)
> > +typeattribute test_key_no_create_t testdomain;
> > +typeattribute test_key_no_create_t keydomain;
> > +
> > +allow test_key_no_create_t self:process { setkeycreate };
> > +allow test_key_no_create_t self:key { write search read view link
> > setattr };
> > +
> > +###################### Deny key { write }
> > ###########################
> > +type test_key_no_write_t;
> > +key_domain_type(test_key_no_write_t)
> > +unconfined_runs_test(test_key_no_write_t)
> > +typeattribute test_key_no_write_t testdomain;
> > +typeattribute test_key_no_write_t keydomain;
> > +
> > +allow test_key_no_write_t self:process { setkeycreate };
> > +allow test_key_no_write_t self:key { create search read view link
> > setattr };
> > +
> > +###################### Deny key { search }
> > ###########################
> > +type test_key_no_search_t;
> > +key_domain_type(test_key_no_search_t)
> > +unconfined_runs_test(test_key_no_search_t)
> > +typeattribute test_key_no_search_t testdomain;
> > +typeattribute test_key_no_search_t keydomain;
> > +
> > +allow test_key_no_search_t self:process { setkeycreate };
> > +allow test_key_no_search_t self:key { create write read view link
> > setattr };
> > +
> > +###################### Deny key { view }
> > ###########################
> > +type test_key_no_view_t;
> > +key_domain_type(test_key_no_view_t)
> > +unconfined_runs_test(test_key_no_view_t)
> > +typeattribute test_key_no_view_t testdomain;
> > +typeattribute test_key_no_view_t keydomain;
> > +
> > +allow test_key_no_view_t self:process { setkeycreate };
> > +allow test_key_no_view_t self:key { create write search read link
> > setattr };
> > +
> > +###################### Deny key { read }
> > ###########################
> > +type test_key_no_read_t;
> > +key_domain_type(test_key_no_read_t)
> > +unconfined_runs_test(test_key_no_read_t)
> > +typeattribute test_key_no_read_t testdomain;
> > +typeattribute test_key_no_read_t keydomain;
> > +
> > +allow test_key_no_read_t self:process { setkeycreate };
> > +allow test_key_no_read_t self:key { create write search view link
> > setattr };
> > +
> > +###################### Deny key { link }
> > ###########################
> > +type test_key_no_link_t;
> > +key_domain_type(test_key_no_link_t)
> > +unconfined_runs_test(test_key_no_link_t)
> > +typeattribute test_key_no_link_t testdomain;
> > +typeattribute test_key_no_link_t keydomain;
> > +
> > +allow test_key_no_link_t self:process { setkeycreate };
> > +allow test_key_no_link_t self:key { create write search read view
> > setattr };
> > +
> > +###################### Deny key { setattr }
> > ###########################
> > +type test_key_no_setattr_t;
> > +key_domain_type(test_key_no_setattr_t)
> > +unconfined_runs_test(test_key_no_setattr_t)
> > +typeattribute test_key_no_setattr_t testdomain;
> > +typeattribute test_key_no_setattr_t keydomain;
> > +
> > +allow test_key_no_setattr_t self:process { setkeycreate };
> > +allow test_key_no_setattr_t self:key { create write search read
> > view link };
> > +
> > +#
> > +######################## Test key_socket class
> > ###########################
> > +#
> > +type test_key_sock_t;
> > +domain_type(test_key_sock_t)
> > +unconfined_runs_test(test_key_sock_t)
> > +typeattribute test_key_sock_t testdomain;
> > +typeattribute test_key_sock_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_t self:rawip_socket { create };
> > +allow test_key_sock_t self:capability { net_admin };
> > +allow test_key_sock_t self:key_socket { create write };
> > +# For CONFIG_NET_KEY=m
> > +allow test_key_sock_t kernel_t:system { module_request };
> > +
> > +################## Deny capability { net_admin }
> > ##########################
> > +type test_key_sock_no_net_admin_t;
> > +domain_type(test_key_sock_no_net_admin_t)
> > +unconfined_runs_test(test_key_sock_no_net_admin_t)
> > +typeattribute test_key_sock_no_net_admin_t testdomain;
> > +typeattribute test_key_sock_no_net_admin_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_no_net_admin_t self:rawip_socket { create };
> > +allow test_key_sock_no_net_admin_t self:key_socket { create write
> > };
> > +
> > +####################### Deny key_socket { create }
> > ##########################
> > +type test_key_sock_no_create_t;
> > +domain_type(test_key_sock_no_create_t)
> > +unconfined_runs_test(test_key_sock_no_create_t)
> > +typeattribute test_key_sock_no_create_t testdomain;
> > +typeattribute test_key_sock_no_create_t keydomain;
> > +
> > +# key_socket rules:
> > +allow test_key_sock_no_create_t self:rawip_socket { create };
> > +allow test_key_sock_no_create_t self:capability { net_admin };
> > +allow test_key_sock_no_create_t self:key_socket { write };
> > +
> > +#
> > +########### Allow these domains to be entered from sysadm domain
> > ############
> > +#
> > +miscfiles_domain_entry_test_files(keydomain)
> > +userdom_sysadm_entry_spec_domtrans_to(keydomain)
> > diff --git a/tests/Makefile b/tests/Makefile
> > index 63aa325..d1dbf38 100644
> > --- a/tests/Makefile
> > +++ b/tests/Makefile
> > @@ -42,6 +42,10 @@ ifeq ($(shell grep -q binder
> > $(POLDEV)/include/support/all_perms.spt && test -e
> >  SUBDIRS += binder
> >  endif
> > 
> > +ifeq ($(shell grep -q key_socket
> > $(POLDEV)/include/support/all_perms.spt && test -e
> > $(INCLUDEDIR)/keyutils.h && echo true),true)
> > +SUBDIRS += keys
> > +endif
> > +
> >  ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST="
> > infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
> >  SUBDIRS += infiniband_endport
> >  endif
> > diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
> > new file mode 100644
> > index 0000000..4a0a48d
> > --- /dev/null
> > +++ b/tests/keys/.gitignore
> > @@ -0,0 +1,3 @@
> > +keyctl
> > +keyctl_relabel
> > +key_sock
> > diff --git a/tests/keys/Makefile b/tests/keys/Makefile
> > new file mode 100644
> > index 0000000..3a00df5
> > --- /dev/null
> > +++ b/tests/keys/Makefile
> > @@ -0,0 +1,8 @@
> > +TARGETS = keyctl key_sock keyctl_relabel
> > +
> > +LDLIBS += -lselinux -lkeyutils
> > +
> > +all: $(TARGETS)
> > +
> > +clean:
> > +       rm -f $(TARGETS)
> > diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
> > new file mode 100644
> > index 0000000..8ac1f45
> > --- /dev/null
> > +++ b/tests/keys/key_sock.c
> > @@ -0,0 +1,67 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <sys/socket.h>
> > +#include <linux/pfkeyv2.h>
> > +#include <selinux/selinux.h>
> > +
> > +static void 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[])
> > +{
> > +       char *context;
> > +       int opt, sock, result;
> > +       bool verbose = false;
> > +
> > +       while ((opt = getopt(argc, argv, "v")) != -1) {
> > +               switch (opt) {
> > +               case 'v':
> > +                       verbose = true;
> > +                       break;
> > +               default:
> > +                       usage(argv[0]);
> > +               }
> > +       }
> > +
> > +       result = getcon(&context);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain process
> > context\n");
> > +               exit(-1);
> > +       }
> > +       if (verbose)
> > +               printf("Process context: %s\n", context);
> > +
> > +       sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
> > +       if (sock < 0) {
> > +               fprintf(stderr, "Failed to open PF_KEY socket:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(errno);
> > +       }
> > +       if (verbose)
> > +               printf("Opened PF_KEY socket\n");
> > +
> > +       /* Write nothing to socket for test, expect EMSGSIZE error
> > */
> > +       result = write(sock, NULL, 0);
> > +       if (result < 0 && errno == EMSGSIZE) {
> > +               result = 0;
> > +               if (verbose)
> > +                       printf("Written to PF_KEY socket\n");
> > +       } else if (result < 0 && errno != EMSGSIZE) {
> > +               result = -1;
> > +               fprintf(stderr, "Failed write to PF_KEY socket:
> > %s\n",
> > +                       strerror(errno));
> > +       }
> > +
> > +       close(sock);
> > +       return result;
> > +}
> > diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
> > new file mode 100644
> > index 0000000..6d85be7
> > --- /dev/null
> > +++ b/tests/keys/keyctl.c
> > @@ -0,0 +1,241 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <keyutils.h>
> > +#include <selinux/selinux.h>
> > +
> > +/* This is used as the payload for each add_key() */
> > +static const char payload[] =
> > +       " -----BEGIN PUBLIC KEY-----
> > \nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\
> > nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\n
> > Ww4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4
> > aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
> > +
> > +static void 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, result;
> > +       char *context, *keycreate_con;
> > +       char r_con[256];
> > +       bool verbose = false;
> > +       key_serial_t retrieved, search, link, compute, newring,
> > +                    private, prime, base, test_key;
> > +       struct keyctl_dh_params params;
> > +
> > +       while ((opt = getopt(argc, argv, "v")) != -1) {
> > +               switch (opt) {
> > +               case 'v':
> > +                       verbose = true;
> > +                       break;
> > +               default:
> > +                       usage(argv[0]);
> > +               }
> > +       }
> > +
> > +       result = getcon(&context);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain process
> > context\n");
> > +               exit(1);
> > +       }
> > +       if (verbose)
> > +               printf("Process context: %s\n", context);
> > +
> > +       result = getkeycreatecon(&keycreate_con);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +               exit(2);
> > +       }
> > +       if (verbose)
> > +               printf("Current keycreate context: %s\n",
> > keycreate_con);
> > +       free(keycreate_con);
> > +
> > +       /* Set context requires process { setkeycreate } and key {
> > create } */
> > +       result = setkeycreatecon(context);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> > +                       strerror(errno));
> > +               exit(3);
> > +       }
> > +       if (verbose)
> > +               printf("Set keycreate context: %s\n", context);
> > +       free(context);
> > +
> > +       result = getkeycreatecon(&keycreate_con);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +               exit(4);
> > +       }
> > +       if (verbose)
> > +               printf("New keycreate context: %s\n",
> > keycreate_con);
> > +       free(keycreate_con);
> > +
> > +       /*
> > +        * Add three keys as these will be required by the
> > +        * keyctl(KEYCTL_DH_COMPUTE, ..) function.
> > +        * These require key { create write } permissions.
> > +        */
> > +       private = add_key("user", "private", payload,
> > strlen(payload),
> > +                         KEY_SPEC_PROCESS_KEYRING);
> > +       if (private < 0) {
> > +               fprintf(stderr, "Failed add_key(private): %s\n",
> > +                       strerror(errno));
> > +               exit(5);
> > +       }
> > +
> > +       prime = add_key("user", "prime", payload, strlen(payload),
> > +                       KEY_SPEC_PROCESS_KEYRING);
> > +       if (prime < 0) {
> > +               fprintf(stderr, "Failed add_key(prime): %s\n",
> > +                       strerror(errno));
> > +               exit(6);
> > +       }
> > +
> > +       base = add_key("user", "base", payload, strlen(payload),
> > +                      KEY_SPEC_PROCESS_KEYRING);
> > +       if (base < 0) {
> > +               fprintf(stderr, "Failed add_key(base): %s\n",
> > +                       strerror(errno));
> > +               exit(7);
> > +       }
> > +
> > +       if (verbose) {
> > +               printf("Private key ID: 0x%x\n", private);
> > +               printf("Prime key ID:   0x%x\n", prime);
> > +               printf("Base key ID:    0x%x\n", base);
> > +       }
> > +
> > +       /* Requires key { search }. From kernel 5.3 also requires {
> > link } */
> > +       retrieved = request_key("user", "private", NULL,
> > +                               KEY_SPEC_PROCESS_KEYRING);
> > +       if (retrieved < 0) {
> > +               fprintf(stderr, "Failed to request 'private' key:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(8);
> > +       }
> > +
> > +       /* Requires key { search } */
> > +       search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING,
> > "user",
> > +                       "base", 0);
> > +       if (search < 0) {
> > +               fprintf(stderr, "Failed to find 'base' key: %s\n",
> > +                       strerror(errno));
> > +               exit(9);
> > +       }
> > +
> > +       /* Requires key { view } */
> > +       result = keyctl(KEYCTL_GET_SECURITY, search, r_con,
> > sizeof(r_con));
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain key context:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(10);
> > +       }
> > +
> > +       if (verbose) {
> > +               printf("Requested 'private' key ID: 0x%x\n",
> > retrieved);
> > +               printf("Searched 'base' key ID:     0x%x\n",
> > search);
> > +               printf("Searched 'base' key context:\n\t%s\n",
> > r_con);
> > +       }
> > +
> > +       /* Compute DH key, only obtain the length for test, not the
> > key. */
> > +       params.priv = private;
> > +       params.prime = prime;
> > +       params.base = base;
> > +
> > +       /* Requires key { create read write } */
> > +       compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
> > +       if (compute < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
> > +                       strerror(errno));
> > +               exit(11);
> > +       }
> > +       if (verbose)
> > +               printf("KEYCTL_DH_COMPUTE key ID size: %d\n",
> > compute);
> > +
> > +       /* To test key { link }, need to generate a new keyring ID
> > first */
> > +       newring = add_key("keyring", "my-keyring", NULL, 0,
> > +                         KEY_SPEC_THREAD_KEYRING);
> > +       if (newring < 0) {
> > +               fprintf(stderr, "Failed to add new keyring: %s\n",
> > +                       strerror(errno));
> > +               exit(12);
> > +       }
> > +       if (verbose)
> > +               printf("New keyring ID: 0x%x\n", newring);
> > +
> > +       /* Requires key { write link } */
> > +       link = keyctl(KEYCTL_LINK, base, newring);
> > +       if (link < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
> > +                       strerror(errno));
> > +               exit(13);
> > +       }
> > +       if (verbose)
> > +               printf("Link key ID:    0x%x\n", newring);
> > +
> > +       /* Requires key { setattr } */
> > +       link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL,
> > NULL);
> > +       if (link < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(14);
> > +       }
> > +       if (verbose)
> > +               printf("Restricted keyring\n");
> > +
> > +       /* Requires key { search } from 5.X key { inval } */
> > +       test_key = keyctl(KEYCTL_INVALIDATE, private);
> > +       if (test_key < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_INVALIDATE(private):
> > %s\n",
> > +                       strerror(errno));
> > +               exit(15);
> > +       }
> > +       if (verbose)
> > +               printf("Invalidated 'private' key\n");
> > +
> > +       /* Requires key { write setattr } from 5.X key { revoke }
> > */
> > +       test_key = keyctl(KEYCTL_REVOKE, prime);
> > +       if (test_key < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_REVOKE(prime):
> > %s\n",
> > +                       strerror(errno));
> > +               exit(16);
> > +       }
> > +       if (verbose)
> > +               printf("Revoked 'prime' key\n");
> > +
> > +       /* Requires key { write } from 5.X key { clear } */
> > +       test_key = keyctl(KEYCTL_CLEAR, newring);
> > +       if (test_key < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_CLEAR(newring):
> > %s\n",
> > +                       strerror(errno));
> > +               exit(17);
> > +       }
> > +       if (verbose)
> > +               printf("Cleared 'newring' keyring\n");
> > +
> > +       /* To test key { join }, need to join session first */
> > +       test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
> > +       if (test_key < 0) {
> > +               fprintf(stderr, "Failed
> > KEYCTL_JOIN_SESSION_KEYRING,: %s\n",
> > +                       strerror(errno));
> > +               exit(18);
> > +       }
> > +       /* Requires key { link } from 5.X key { join } */
> > +       test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
> > +       if (test_key < 0) {
> > +               fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(19);
> > +       }
> > +       if (verbose)
> > +               printf("Joined session to parent\n");
> > +
> > +       return 0;
> > +}
> > diff --git a/tests/keys/keyctl_relabel.c
> > b/tests/keys/keyctl_relabel.c
> > new file mode 100644
> > index 0000000..0276c7a
> > --- /dev/null
> > +++ b/tests/keys/keyctl_relabel.c
> > @@ -0,0 +1,93 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <keyutils.h>
> > +#include <selinux/selinux.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +       fprintf(stderr,
> > +               "usage:  %s [-v] newcon\n"
> > +               "Where:\n\t"
> > +               "-v      Print information.\n\t"
> > +               "newcon  New keyring context.\n", progname);
> > +       exit(-1);
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +       int opt, result;
> > +       char *context, *keycreate_con;
> > +       char r_con[256];
> > +       bool verbose = false;
> > +       key_serial_t newring;
> > +
> > +       while ((opt = getopt(argc, argv, "v")) != -1) {
> > +               switch (opt) {
> > +               case 'v':
> > +                       verbose = true;
> > +                       break;
> > +               default:
> > +                       usage(argv[0]);
> > +               }
> > +       }
> > +
> > +       if (optind >= argc)
> > +               usage(argv[0]);
> > +
> > +       result = getcon(&context);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain process
> > context\n");
> > +               exit(1);
> > +       }
> > +       if (verbose)
> > +               printf("Process context: %s\n", context);
> > +       free(context);
> > +
> > +       result = setkeycreatecon(argv[optind]);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed setkeycreatecon(): %s\n",
> > +                       strerror(errno));
> > +               exit(2);
> > +       }
> > +
> > +       result = getkeycreatecon(&keycreate_con);
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain keycreate
> > context\n");
> > +               exit(3);
> > +       }
> > +       if (verbose)
> > +               printf("New keycreate context: %s\n",
> > keycreate_con);
> > +       free(keycreate_con);
> > +
> > +       newring = add_key("keyring", "my-keyring", NULL, 0,
> > +                         KEY_SPEC_THREAD_KEYRING);
> > +       if (newring < 0) {
> > +               fprintf(stderr, "Failed to add new keyring: %s\n",
> > +                       strerror(errno));
> > +               exit(4);
> > +       }
> > +
> > +       result = keyctl(KEYCTL_GET_SECURITY, newring, r_con,
> > sizeof(r_con));
> > +       if (result < 0) {
> > +               fprintf(stderr, "Failed to obtain key context:
> > %s\n",
> > +                       strerror(errno));
> > +               exit(5);
> > +       }
> > +
> > +       if (strcmp(argv[optind], r_con)) {
> > +               fprintf(stderr, "Relabel error - expected: %s got:
> > %s\n",
> > +                       argv[optind], r_con);
> > +               exit(6);
> > +       }
> > +
> > +       if (verbose) {
> > +               printf("'my-keyring' key ID: 0x%x\n", newring);
> > +               printf("'my-keyring' context:\n\t%s\n", r_con);
> > +       }
> > +
> > +       return 0;
> > +}
> > diff --git a/tests/keys/test b/tests/keys/test
> > new file mode 100755
> > index 0000000..4916e7c
> > --- /dev/null
> > +++ b/tests/keys/test
> > @@ -0,0 +1,98 @@
> > +#!/usr/bin/perl
> > +use Test::More;
> > +
> > +BEGIN {
> > +    $basedir = $0;
> > +    $basedir =~ s|(.*)/[^/]*|$1|;
> > +
> > +    $test_count = 13;
> > +
> > +    # 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 = " ";
> > +    }
> > +
> > +    # From kernel 5.3 request_key() requires additional check of
> > key { link }
> > +    $kvercur = `uname -r`;
> > +    chomp($kvercur);
> > +    $kverminstream = "5.3";
> > +    $test_link_53  = 0;
> > +
> > +    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
> > +    if ( $result >= 0 ) {
> > +        $test_link_53 = 1;
> > +    }
> > +
> > +    plan tests => $test_count;
> > +}
> > +
> > +############ Test keyctl #############
> > +print "Test key class permissions\n";
> > +$result = system "runcon -t test_key_t $basedir/keyctl $v";
> > +ok( $result eq 0 );
> > +
> > +# Deny process { setkeycreate }
> > +$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 3 );
> > +
> > +# Deny key { create }
> > +$result = system "runcon -t test_key_no_create_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 3 );
> > +
> > +# Deny key { write }
> > +$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 5 );
> > +
> > +# Deny key { search }
> > +$result = system "runcon -t test_key_no_search_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 8 );
> > +
> > +# Deny key { view }
> > +$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 10 );
> > +
> > +# Deny key { read }
> > +$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v
> > 2>&1";
> > +ok( $result >> 8 eq 11 );
> > +
> > +# Deny key { link }
> > +$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v
> > 2>&1";
> > +if ($test_link_53) {
> > +    ok( $result >> 8 eq 8 );
> > +}
> > +else {
> > +    ok( $result >> 8 eq 13 );
> > +}
> > +
> > +# Deny key { setattr }
> > +$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl
> > $v 2>&1";
> > +ok( $result >> 8 eq 14 );
> > +
> > +########### Change keyring context ##############
> > +print "Change keyring context\n";
> > +$result = system
> > +"runcon -t test_key_t $basedir/keyctl_relabel $v
> > system_u:system_r:test_newcon_key_t:s0";
> > +ok( $result eq 0 );
> > +
> > +############ Test key_socket #############
> > +print "Test key_socket class\n";
> > +$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
> > +ok( $result eq 0 );
> > +
> > +# Deny capability { net_admin } - EPERM
> > +$result =
> > +  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock
> > $v 2>&1";
> > +ok( $result >> 8 eq 1 );
> > +
> > +# Deny key_socket { create } - EACCES
> > +$result =
> > +  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v
> > 2>&1";
> > +ok( $result >> 8 eq 13 );
> > +
> > +exit;
> > --
> > 2.21.0
> > 
> 
> --
> Ondrej Mosnacek <omosnace at redhat dot com>
> Software Engineer, Security Technologies
> Red Hat, Inc.
>
diff mbox series

Patch

diff --git a/README.md b/README.md
index 26784f8..fe72a91 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,8 @@  following command:
 		netlabel_tools \
 		iptables \
 		lksctp-tools-devel \
-		attr
+		attr \
+		keyutils-libs-devel
 
 The testsuite requires a pre-existing base policy configuration of SELinux,
 using either the old example policy or the reference policy as the baseline.
diff --git a/defconfig b/defconfig
index d7f0ea5..c00e291 100644
--- a/defconfig
+++ b/defconfig
@@ -62,3 +62,11 @@  CONFIG_ANDROID_BINDER_IPC=y
 # This will configure the Dynamically Allocated Binder Devices added
 # to 5.0+ kernels:
 CONFIG_ANDROID_BINDERFS=y
+
+# Key implementations.
+# These are enabled to test the key and key_socket controls in
+# tests/keys; they are not required for SELinux operation itself.
+CONFIG_KEYS=y
+CONFIG_KEYS_COMPAT=y
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_NET_KEY=m
diff --git a/policy/Makefile b/policy/Makefile
index 305b572..9258a93 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -71,6 +71,10 @@  ifeq ($(shell grep -q corenet_sctp_bind_all_nodes $(POLDEV)/include/kernel/coren
 TARGETS += test_sctp.te
 endif
 
+ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && echo true),true)
+TARGETS += test_keys.te
+endif
+
 ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
 TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
 endif
diff --git a/policy/test_keys.te b/policy/test_keys.te
new file mode 100644
index 0000000..9c65ec8
--- /dev/null
+++ b/policy/test_keys.te
@@ -0,0 +1,161 @@ 
+#
+################# KEY selinux-testsuite policy module ######################
+#
+attribute keydomain;
+
+#
+############################## Define Macro ################################
+#
+# Do not use domain_type() macro as it has allow 'key { link search }'
+# in base module so 'allow domain self:key ~{ link search };' will not work
+# here. Add these instead to allow key perms to be controlled by this module:
+#
+define(`key_domain_type',`
+	allow $1 proc_t:dir { search };
+	allow $1 proc_t:lnk_file { read };
+	allow $1 self:dir { search };
+	allow $1 self:file { open read write };
+')
+
+#
+####################### Main key class tests #####################
+#
+type test_key_t;
+key_domain_type(test_key_t)
+unconfined_runs_test(test_key_t)
+typeattribute test_key_t testdomain;
+typeattribute test_key_t keydomain;
+
+allow test_key_t self:process { setkeycreate };
+allow test_key_t self:key { create write search read view link setattr };
+
+# Set new context on a keyring:
+type test_newcon_key_t;
+key_domain_type(test_newcon_key_t)
+unconfined_runs_test(test_newcon_key_t)
+typeattribute test_newcon_key_t testdomain;
+typeattribute test_newcon_key_t keydomain;
+
+allow test_key_t test_newcon_key_t:key { create write search view };
+
+################# Deny process { setkeycreate } #######################
+type test_no_setkeycreate_t;
+key_domain_type(test_no_setkeycreate_t)
+unconfined_runs_test(test_no_setkeycreate_t)
+typeattribute test_no_setkeycreate_t testdomain;
+typeattribute test_no_setkeycreate_t keydomain;
+
+###################### Deny key { create } ###########################
+type test_key_no_create_t;
+key_domain_type(test_key_no_create_t)
+unconfined_runs_test(test_key_no_create_t)
+typeattribute test_key_no_create_t testdomain;
+typeattribute test_key_no_create_t keydomain;
+
+allow test_key_no_create_t self:process { setkeycreate };
+allow test_key_no_create_t self:key { write search read view link setattr };
+
+###################### Deny key { write } ###########################
+type test_key_no_write_t;
+key_domain_type(test_key_no_write_t)
+unconfined_runs_test(test_key_no_write_t)
+typeattribute test_key_no_write_t testdomain;
+typeattribute test_key_no_write_t keydomain;
+
+allow test_key_no_write_t self:process { setkeycreate };
+allow test_key_no_write_t self:key { create search read view link setattr };
+
+###################### Deny key { search } ###########################
+type test_key_no_search_t;
+key_domain_type(test_key_no_search_t)
+unconfined_runs_test(test_key_no_search_t)
+typeattribute test_key_no_search_t testdomain;
+typeattribute test_key_no_search_t keydomain;
+
+allow test_key_no_search_t self:process { setkeycreate };
+allow test_key_no_search_t self:key { create write read view link setattr };
+
+###################### Deny key { view } ###########################
+type test_key_no_view_t;
+key_domain_type(test_key_no_view_t)
+unconfined_runs_test(test_key_no_view_t)
+typeattribute test_key_no_view_t testdomain;
+typeattribute test_key_no_view_t keydomain;
+
+allow test_key_no_view_t self:process { setkeycreate };
+allow test_key_no_view_t self:key { create write search read link setattr };
+
+###################### Deny key { read } ###########################
+type test_key_no_read_t;
+key_domain_type(test_key_no_read_t)
+unconfined_runs_test(test_key_no_read_t)
+typeattribute test_key_no_read_t testdomain;
+typeattribute test_key_no_read_t keydomain;
+
+allow test_key_no_read_t self:process { setkeycreate };
+allow test_key_no_read_t self:key { create write search view link setattr };
+
+###################### Deny key { link } ###########################
+type test_key_no_link_t;
+key_domain_type(test_key_no_link_t)
+unconfined_runs_test(test_key_no_link_t)
+typeattribute test_key_no_link_t testdomain;
+typeattribute test_key_no_link_t keydomain;
+
+allow test_key_no_link_t self:process { setkeycreate };
+allow test_key_no_link_t self:key { create write search read view setattr };
+
+###################### Deny key { setattr } ###########################
+type test_key_no_setattr_t;
+key_domain_type(test_key_no_setattr_t)
+unconfined_runs_test(test_key_no_setattr_t)
+typeattribute test_key_no_setattr_t testdomain;
+typeattribute test_key_no_setattr_t keydomain;
+
+allow test_key_no_setattr_t self:process { setkeycreate };
+allow test_key_no_setattr_t self:key { create write search read view link };
+
+#
+######################## Test key_socket class ###########################
+#
+type test_key_sock_t;
+domain_type(test_key_sock_t)
+unconfined_runs_test(test_key_sock_t)
+typeattribute test_key_sock_t testdomain;
+typeattribute test_key_sock_t keydomain;
+
+# key_socket rules:
+allow test_key_sock_t self:rawip_socket { create };
+allow test_key_sock_t self:capability { net_admin };
+allow test_key_sock_t self:key_socket { create write };
+# For CONFIG_NET_KEY=m
+allow test_key_sock_t kernel_t:system { module_request };
+
+################## Deny capability { net_admin } ##########################
+type test_key_sock_no_net_admin_t;
+domain_type(test_key_sock_no_net_admin_t)
+unconfined_runs_test(test_key_sock_no_net_admin_t)
+typeattribute test_key_sock_no_net_admin_t testdomain;
+typeattribute test_key_sock_no_net_admin_t keydomain;
+
+# key_socket rules:
+allow test_key_sock_no_net_admin_t self:rawip_socket { create };
+allow test_key_sock_no_net_admin_t self:key_socket { create write };
+
+####################### Deny key_socket { create } ##########################
+type test_key_sock_no_create_t;
+domain_type(test_key_sock_no_create_t)
+unconfined_runs_test(test_key_sock_no_create_t)
+typeattribute test_key_sock_no_create_t testdomain;
+typeattribute test_key_sock_no_create_t keydomain;
+
+# key_socket rules:
+allow test_key_sock_no_create_t self:rawip_socket { create };
+allow test_key_sock_no_create_t self:capability { net_admin };
+allow test_key_sock_no_create_t self:key_socket { write };
+
+#
+########### Allow these domains to be entered from sysadm domain ############
+#
+miscfiles_domain_entry_test_files(keydomain)
+userdom_sysadm_entry_spec_domtrans_to(keydomain)
diff --git a/tests/Makefile b/tests/Makefile
index 63aa325..d1dbf38 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -42,6 +42,10 @@  ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && test -e
 SUBDIRS += binder
 endif
 
+ifeq ($(shell grep -q key_socket $(POLDEV)/include/support/all_perms.spt && test -e $(INCLUDEDIR)/keyutils.h && echo true),true)
+SUBDIRS += keys
+endif
+
 ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
 SUBDIRS += infiniband_endport
 endif
diff --git a/tests/keys/.gitignore b/tests/keys/.gitignore
new file mode 100644
index 0000000..4a0a48d
--- /dev/null
+++ b/tests/keys/.gitignore
@@ -0,0 +1,3 @@ 
+keyctl
+keyctl_relabel
+key_sock
diff --git a/tests/keys/Makefile b/tests/keys/Makefile
new file mode 100644
index 0000000..3a00df5
--- /dev/null
+++ b/tests/keys/Makefile
@@ -0,0 +1,8 @@ 
+TARGETS = keyctl key_sock keyctl_relabel
+
+LDLIBS += -lselinux -lkeyutils
+
+all: $(TARGETS)
+
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/keys/key_sock.c b/tests/keys/key_sock.c
new file mode 100644
index 0000000..8ac1f45
--- /dev/null
+++ b/tests/keys/key_sock.c
@@ -0,0 +1,67 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <linux/pfkeyv2.h>
+#include <selinux/selinux.h>
+
+static void 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[])
+{
+	char *context;
+	int opt, sock, result;
+	bool verbose = false;
+
+	while ((opt = getopt(argc, argv, "v")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	result = getcon(&context);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain process context\n");
+		exit(-1);
+	}
+	if (verbose)
+		printf("Process context: %s\n", context);
+
+	sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
+	if (sock < 0) {
+		fprintf(stderr, "Failed to open PF_KEY socket: %s\n",
+			strerror(errno));
+		exit(errno);
+	}
+	if (verbose)
+		printf("Opened PF_KEY socket\n");
+
+	/* Write nothing to socket for test, expect EMSGSIZE error */
+	result = write(sock, NULL, 0);
+	if (result < 0 && errno == EMSGSIZE) {
+		result = 0;
+		if (verbose)
+			printf("Written to PF_KEY socket\n");
+	} else if (result < 0 && errno != EMSGSIZE) {
+		result = -1;
+		fprintf(stderr, "Failed write to PF_KEY socket: %s\n",
+			strerror(errno));
+	}
+
+	close(sock);
+	return result;
+}
diff --git a/tests/keys/keyctl.c b/tests/keys/keyctl.c
new file mode 100644
index 0000000..6d85be7
--- /dev/null
+++ b/tests/keys/keyctl.c
@@ -0,0 +1,241 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <keyutils.h>
+#include <selinux/selinux.h>
+
+/* This is used as the payload for each add_key() */
+static const char payload[] =
+	" -----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN4FHsPjlJf03r9KfNt1Ma9/D6\nQDEiR/cfhZrNUPgHRrC+E4dj52VJSonPFJ6HaLlUi5pZq2t1LqPNrMfFKCNn12m+\nWw4aduBJM7u1RUPSNxrlfDAJZkdtNALOO/ds3U93hZrxOYNetzbnjILDu5JT1nbI\n4aC60SkdlCw1TxmvXwIDAQAB\n-----END PUBLIC KEY-----\n";
+
+static void 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, result;
+	char *context, *keycreate_con;
+	char r_con[256];
+	bool verbose = false;
+	key_serial_t retrieved, search, link, compute, newring,
+		     private, prime, base, test_key;
+	struct keyctl_dh_params params;
+
+	while ((opt = getopt(argc, argv, "v")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	result = getcon(&context);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain process context\n");
+		exit(1);
+	}
+	if (verbose)
+		printf("Process context: %s\n", context);
+
+	result = getkeycreatecon(&keycreate_con);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain keycreate context\n");
+		exit(2);
+	}
+	if (verbose)
+		printf("Current keycreate context: %s\n", keycreate_con);
+	free(keycreate_con);
+
+	/* Set context requires process { setkeycreate } and key { create } */
+	result = setkeycreatecon(context);
+	if (result < 0) {
+		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
+			strerror(errno));
+		exit(3);
+	}
+	if (verbose)
+		printf("Set keycreate context: %s\n", context);
+	free(context);
+
+	result = getkeycreatecon(&keycreate_con);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain keycreate context\n");
+		exit(4);
+	}
+	if (verbose)
+		printf("New keycreate context: %s\n", keycreate_con);
+	free(keycreate_con);
+
+	/*
+	 * Add three keys as these will be required by the
+	 * keyctl(KEYCTL_DH_COMPUTE, ..) function.
+	 * These require key { create write } permissions.
+	 */
+	private = add_key("user", "private", payload, strlen(payload),
+			  KEY_SPEC_PROCESS_KEYRING);
+	if (private < 0) {
+		fprintf(stderr, "Failed add_key(private): %s\n",
+			strerror(errno));
+		exit(5);
+	}
+
+	prime = add_key("user", "prime", payload, strlen(payload),
+			KEY_SPEC_PROCESS_KEYRING);
+	if (prime < 0) {
+		fprintf(stderr, "Failed add_key(prime): %s\n",
+			strerror(errno));
+		exit(6);
+	}
+
+	base = add_key("user", "base", payload, strlen(payload),
+		       KEY_SPEC_PROCESS_KEYRING);
+	if (base < 0) {
+		fprintf(stderr, "Failed add_key(base): %s\n",
+			strerror(errno));
+		exit(7);
+	}
+
+	if (verbose) {
+		printf("Private key ID: 0x%x\n", private);
+		printf("Prime key ID:   0x%x\n", prime);
+		printf("Base key ID:    0x%x\n", base);
+	}
+
+	/* Requires key { search }. From kernel 5.3 also requires { link } */
+	retrieved = request_key("user", "private", NULL,
+				KEY_SPEC_PROCESS_KEYRING);
+	if (retrieved < 0) {
+		fprintf(stderr, "Failed to request 'private' key: %s\n",
+			strerror(errno));
+		exit(8);
+	}
+
+	/* Requires key { search } */
+	search = keyctl(KEYCTL_SEARCH, KEY_SPEC_PROCESS_KEYRING, "user",
+			"base", 0);
+	if (search < 0) {
+		fprintf(stderr, "Failed to find 'base' key: %s\n",
+			strerror(errno));
+		exit(9);
+	}
+
+	/* Requires key { view } */
+	result = keyctl(KEYCTL_GET_SECURITY, search, r_con, sizeof(r_con));
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain key context: %s\n",
+			strerror(errno));
+		exit(10);
+	}
+
+	if (verbose) {
+		printf("Requested 'private' key ID: 0x%x\n", retrieved);
+		printf("Searched 'base' key ID:     0x%x\n", search);
+		printf("Searched 'base' key context:\n\t%s\n", r_con);
+	}
+
+	/* Compute DH key, only obtain the length for test, not the key. */
+	params.priv = private;
+	params.prime = prime;
+	params.base = base;
+
+	/* Requires key { create read write } */
+	compute = keyctl(KEYCTL_DH_COMPUTE, &params, NULL, 0, 0);
+	if (compute < 0) {
+		fprintf(stderr, "Failed KEYCTL_DH_COMPUTE: %s\n",
+			strerror(errno));
+		exit(11);
+	}
+	if (verbose)
+		printf("KEYCTL_DH_COMPUTE key ID size: %d\n", compute);
+
+	/* To test key { link }, need to generate a new keyring ID first */
+	newring = add_key("keyring", "my-keyring", NULL, 0,
+			  KEY_SPEC_THREAD_KEYRING);
+	if (newring < 0) {
+		fprintf(stderr, "Failed to add new keyring: %s\n",
+			strerror(errno));
+		exit(12);
+	}
+	if (verbose)
+		printf("New keyring ID: 0x%x\n", newring);
+
+	/* Requires key { write link } */
+	link = keyctl(KEYCTL_LINK, base, newring);
+	if (link < 0) {
+		fprintf(stderr, "Failed KEYCTL_LINK: %s\n",
+			strerror(errno));
+		exit(13);
+	}
+	if (verbose)
+		printf("Link key ID:    0x%x\n", newring);
+
+	/* Requires key { setattr } */
+	link = keyctl(KEYCTL_RESTRICT_KEYRING, newring, NULL, NULL);
+	if (link < 0) {
+		fprintf(stderr, "Failed KEYCTL_RESTRICT_KEYRING: %s\n",
+			strerror(errno));
+		exit(14);
+	}
+	if (verbose)
+		printf("Restricted keyring\n");
+
+	/* Requires key { search } from 5.X key { inval } */
+	test_key = keyctl(KEYCTL_INVALIDATE, private);
+	if (test_key < 0) {
+		fprintf(stderr, "Failed KEYCTL_INVALIDATE(private): %s\n",
+			strerror(errno));
+		exit(15);
+	}
+	if (verbose)
+		printf("Invalidated 'private' key\n");
+
+	/* Requires key { write setattr } from 5.X key { revoke } */
+	test_key = keyctl(KEYCTL_REVOKE, prime);
+	if (test_key < 0) {
+		fprintf(stderr, "Failed KEYCTL_REVOKE(prime): %s\n",
+			strerror(errno));
+		exit(16);
+	}
+	if (verbose)
+		printf("Revoked 'prime' key\n");
+
+	/* Requires key { write } from 5.X key { clear } */
+	test_key = keyctl(KEYCTL_CLEAR, newring);
+	if (test_key < 0) {
+		fprintf(stderr, "Failed KEYCTL_CLEAR(newring): %s\n",
+			strerror(errno));
+		exit(17);
+	}
+	if (verbose)
+		printf("Cleared 'newring' keyring\n");
+
+	/* To test key { join }, need to join session first */
+	test_key = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "user");
+	if (test_key < 0) {
+		fprintf(stderr, "Failed KEYCTL_JOIN_SESSION_KEYRING,: %s\n",
+			strerror(errno));
+		exit(18);
+	}
+	/* Requires key { link } from 5.X key { join } */
+	test_key = keyctl(KEYCTL_SESSION_TO_PARENT);
+	if (test_key < 0) {
+		fprintf(stderr, "Failed KEYCTL_SESSION_TO_PARENT: %s\n",
+			strerror(errno));
+		exit(19);
+	}
+	if (verbose)
+		printf("Joined session to parent\n");
+
+	return 0;
+}
diff --git a/tests/keys/keyctl_relabel.c b/tests/keys/keyctl_relabel.c
new file mode 100644
index 0000000..0276c7a
--- /dev/null
+++ b/tests/keys/keyctl_relabel.c
@@ -0,0 +1,93 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <keyutils.h>
+#include <selinux/selinux.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-v] newcon\n"
+		"Where:\n\t"
+		"-v      Print information.\n\t"
+		"newcon  New keyring context.\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char *argv[])
+{
+	int opt, result;
+	char *context, *keycreate_con;
+	char r_con[256];
+	bool verbose = false;
+	key_serial_t newring;
+
+	while ((opt = getopt(argc, argv, "v")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc)
+		usage(argv[0]);
+
+	result = getcon(&context);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain process context\n");
+		exit(1);
+	}
+	if (verbose)
+		printf("Process context: %s\n", context);
+	free(context);
+
+	result = setkeycreatecon(argv[optind]);
+	if (result < 0) {
+		fprintf(stderr, "Failed setkeycreatecon(): %s\n",
+			strerror(errno));
+		exit(2);
+	}
+
+	result = getkeycreatecon(&keycreate_con);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain keycreate context\n");
+		exit(3);
+	}
+	if (verbose)
+		printf("New keycreate context: %s\n", keycreate_con);
+	free(keycreate_con);
+
+	newring = add_key("keyring", "my-keyring", NULL, 0,
+			  KEY_SPEC_THREAD_KEYRING);
+	if (newring < 0) {
+		fprintf(stderr, "Failed to add new keyring: %s\n",
+			strerror(errno));
+		exit(4);
+	}
+
+	result = keyctl(KEYCTL_GET_SECURITY, newring, r_con, sizeof(r_con));
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain key context: %s\n",
+			strerror(errno));
+		exit(5);
+	}
+
+	if (strcmp(argv[optind], r_con)) {
+		fprintf(stderr, "Relabel error - expected: %s got: %s\n",
+			argv[optind], r_con);
+		exit(6);
+	}
+
+	if (verbose) {
+		printf("'my-keyring' key ID: 0x%x\n", newring);
+		printf("'my-keyring' context:\n\t%s\n", r_con);
+	}
+
+	return 0;
+}
diff --git a/tests/keys/test b/tests/keys/test
new file mode 100755
index 0000000..4916e7c
--- /dev/null
+++ b/tests/keys/test
@@ -0,0 +1,98 @@ 
+#!/usr/bin/perl
+use Test::More;
+
+BEGIN {
+    $basedir = $0;
+    $basedir =~ s|(.*)/[^/]*|$1|;
+
+    $test_count = 13;
+
+    # 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 = " ";
+    }
+
+    # From kernel 5.3 request_key() requires additional check of key { link }
+    $kvercur = `uname -r`;
+    chomp($kvercur);
+    $kverminstream = "5.3";
+    $test_link_53  = 0;
+
+    $result = `$basedir/../kvercmp $kvercur $kverminstream`;
+    if ( $result >= 0 ) {
+        $test_link_53 = 1;
+    }
+
+    plan tests => $test_count;
+}
+
+############ Test keyctl #############
+print "Test key class permissions\n";
+$result = system "runcon -t test_key_t $basedir/keyctl $v";
+ok( $result eq 0 );
+
+# Deny process { setkeycreate }
+$result = system "runcon -t test_no_setkeycreate_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 3 );
+
+# Deny key { create }
+$result = system "runcon -t test_key_no_create_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 3 );
+
+# Deny key { write }
+$result = system "runcon -t test_key_no_write_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 5 );
+
+# Deny key { search }
+$result = system "runcon -t test_key_no_search_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 8 );
+
+# Deny key { view }
+$result = system "runcon -t test_key_no_view_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 10 );
+
+# Deny key { read }
+$result = system "runcon -t test_key_no_read_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 11 );
+
+# Deny key { link }
+$result = system "runcon -t test_key_no_link_t $basedir/keyctl $v 2>&1";
+if ($test_link_53) {
+    ok( $result >> 8 eq 8 );
+}
+else {
+    ok( $result >> 8 eq 13 );
+}
+
+# Deny key { setattr }
+$result = system "runcon -t test_key_no_setattr_t $basedir/keyctl $v 2>&1";
+ok( $result >> 8 eq 14 );
+
+########### Change keyring context ##############
+print "Change keyring context\n";
+$result = system
+"runcon -t test_key_t $basedir/keyctl_relabel $v system_u:system_r:test_newcon_key_t:s0";
+ok( $result eq 0 );
+
+############ Test key_socket #############
+print "Test key_socket class\n";
+$result = system "runcon -t test_key_sock_t $basedir/key_sock $v";
+ok( $result eq 0 );
+
+# Deny capability { net_admin } - EPERM
+$result =
+  system "runcon -t test_key_sock_no_net_admin_t $basedir/key_sock $v 2>&1";
+ok( $result >> 8 eq 1 );
+
+# Deny key_socket { create } - EACCES
+$result =
+  system "runcon -t test_key_sock_no_create_t $basedir/key_sock $v 2>&1";
+ok( $result >> 8 eq 13 );
+
+exit;