[RFC,V3] selinux-testsuite: Add test for restorecon
diff mbox series

Message ID 20190603095609.21429-1-richard_c_haines@btinternet.com
State Rejected
Headers show
Series
  • [RFC,V3] selinux-testsuite: Add test for restorecon
Related show

Commit Message

Richard Haines June 3, 2019, 9:56 a.m. UTC
This will test the updated libselinux selinux_restorecon(3) using a
simple test version of "restorecon", or if the test is run locally,
a '-p' option can be used to supply the path of a full version of
restorecon(8) (see note in restorecon/test).

As the SELinux testsuite is primarily a set of regression tests for the
SELinux kernel, this change also adds support to include the testing of
core userspace services such as library functions or utilities. To
install and run these type of tests, the following must be run first:

	# export TEST_USERSPACE=y

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
---
V2 Changes:
Allow option to run other restorecon binaries.
Only run if userspace tests enabled.
Add tests for "Ignore the stem..." patch
V3 Changes:
Add tests for No CAP_SYS_ADMIN permission and SKIP_DIGEST

 README.md                                  |   7 +
 policy/Makefile                            |   8 +
 policy/test_restorecon.te                  |  74 ++++++++
 tests/Makefile                             |   7 +
 tests/file/test                            |   2 +-
 tests/restorecon/.gitignore                |   5 +
 tests/restorecon/Makefile                  |   7 +
 tests/restorecon/check_fs.c                |  69 ++++++++
 tests/restorecon/get_all_digests.c         | 176 +++++++++++++++++++
 tests/restorecon/restorecon.c              |  80 +++++++++
 tests/restorecon/restorecon_xattr.c        | 116 +++++++++++++
 tests/restorecon/selinux_restorecon_skip.c |  66 ++++++++
 tests/restorecon/test                      | 188 +++++++++++++++++++++
 13 files changed, 804 insertions(+), 1 deletion(-)
 create mode 100644 policy/test_restorecon.te
 create mode 100644 tests/restorecon/.gitignore
 create mode 100644 tests/restorecon/Makefile
 create mode 100644 tests/restorecon/check_fs.c
 create mode 100644 tests/restorecon/get_all_digests.c
 create mode 100644 tests/restorecon/restorecon.c
 create mode 100644 tests/restorecon/restorecon_xattr.c
 create mode 100644 tests/restorecon/selinux_restorecon_skip.c
 create mode 100755 tests/restorecon/test

Comments

Stephen Smalley Sept. 13, 2019, 5:09 p.m. UTC | #1
On 6/3/19 5:56 AM, Richard Haines wrote:
> This will test the updated libselinux selinux_restorecon(3) using a
> simple test version of "restorecon", or if the test is run locally,
> a '-p' option can be used to supply the path of a full version of
> restorecon(8) (see note in restorecon/test).
> 
> As the SELinux testsuite is primarily a set of regression tests for the
> SELinux kernel, this change also adds support to include the testing of
> core userspace services such as library functions or utilities. To
> install and run these type of tests, the following must be run first:
> 
> 	# export TEST_USERSPACE=y

Sorry for the long delay in responding. I would prefer userspace tests 
to remain part of the selinux userspace repository, invoked upon make 
test there.  A test policy if required should be possible via a separate 
standalone cil or te/fc policy module that doesn't depend on the 
testsuite policy.

> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
> V2 Changes:
> Allow option to run other restorecon binaries.
> Only run if userspace tests enabled.
> Add tests for "Ignore the stem..." patch
> V3 Changes:
> Add tests for No CAP_SYS_ADMIN permission and SKIP_DIGEST
> 
>   README.md                                  |   7 +
>   policy/Makefile                            |   8 +
>   policy/test_restorecon.te                  |  74 ++++++++
>   tests/Makefile                             |   7 +
>   tests/file/test                            |   2 +-
>   tests/restorecon/.gitignore                |   5 +
>   tests/restorecon/Makefile                  |   7 +
>   tests/restorecon/check_fs.c                |  69 ++++++++
>   tests/restorecon/get_all_digests.c         | 176 +++++++++++++++++++
>   tests/restorecon/restorecon.c              |  80 +++++++++
>   tests/restorecon/restorecon_xattr.c        | 116 +++++++++++++
>   tests/restorecon/selinux_restorecon_skip.c |  66 ++++++++
>   tests/restorecon/test                      | 188 +++++++++++++++++++++
>   13 files changed, 804 insertions(+), 1 deletion(-)
>   create mode 100644 policy/test_restorecon.te
>   create mode 100644 tests/restorecon/.gitignore
>   create mode 100644 tests/restorecon/Makefile
>   create mode 100644 tests/restorecon/check_fs.c
>   create mode 100644 tests/restorecon/get_all_digests.c
>   create mode 100644 tests/restorecon/restorecon.c
>   create mode 100644 tests/restorecon/restorecon_xattr.c
>   create mode 100644 tests/restorecon/selinux_restorecon_skip.c
>   create mode 100755 tests/restorecon/test
> 
> diff --git a/README.md b/README.md
> index 26784f8..329a495 100644
> --- a/README.md
> +++ b/README.md
> @@ -114,6 +114,13 @@ the tests:
>   
>   ## Running the Tests
>   
> +The SELinux testsuite is primarily a set of regression tests for the SELinux
> +kernel, however it is possible to include the testing of core userspace
> +services such as library functions or utilities. To install any relevant
> +tests, the following must be run first:
> +
> +	# export TEST_USERSPACE=y
> +
>   Create a shell with the `unconfined_r` or `sysadm_r` role and the Linux
>   superuser identity, e.g.:
>   
> diff --git a/policy/Makefile b/policy/Makefile
> index 305b572..9c7d173 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -3,6 +3,7 @@ POLDEV ?= /usr/share/selinux/devel
>   SEMODULE = /usr/sbin/semodule
>   CHECKPOLICY = /usr/bin/checkpolicy
>   CHECKMODULE = /usr/bin/checkmodule
> +INCLUDEDIR ?= /usr/include
>   
>   DISTRO=$(shell ../tests/os_detect)
>   RHEL_VERS=$(shell echo $(DISTRO) | sed 's/RHEL//')
> @@ -79,6 +80,13 @@ ifeq (x$(DISTRO),$(filter x$(DISTRO), xRHEL6))
>   TARGETS:=$(filter-out test_ibpkey.te, $(TARGETS))
>   endif
>   
> +# Add any userspace test policy
> +ifeq ($(TEST_USERSPACE),y)
> +    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
> +        TARGETS += test_restorecon.te
> +    endif
> +endif
> +
>   ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5))
>   	BUILD_TARGET := build_rhel
>   	LOAD_TARGET := load_rhel
> diff --git a/policy/test_restorecon.te b/policy/test_restorecon.te
> new file mode 100644
> index 0000000..57699bb
> --- /dev/null
> +++ b/policy/test_restorecon.te
> @@ -0,0 +1,74 @@
> +#################################
> +#
> +# Policy for testing restorecon
> +#
> +
> +require {
> +	attribute file_type;
> +	type setfiles_exec_t;
> +}
> +
> +attribute restorecon_domain;
> +
> +type test_restorecon_file_t;
> +files_type(test_restorecon_file_t)
> +type in_dir_t;
> +files_type(in_dir_t)
> +type out_dir_t;
> +files_type(out_dir_t)
> +type in_file_t;
> +files_type(in_file_t)
> +type out_file_t;
> +files_type(out_file_t)
> +
> +# Domain for restorecon.
> +type test_restorecon_t;
> +files_type(test_restorecon_t)
> +
> +domain_type(test_restorecon_t)
> +unconfined_runs_test(test_restorecon_t)
> +typeattribute test_restorecon_t testdomain;
> +typeattribute test_restorecon_t restorecon_domain;
> +
> +allow test_restorecon_t self:capability sys_admin;
> +allow test_restorecon_t test_file_t:file relabelfrom;
> +allow test_restorecon_t file_type:dir { relabel_dir_perms manage_dir_perms };
> +allow test_restorecon_t file_type:file { rw_file_perms execute relabelto relabelfrom };
> +allow_map(test_restorecon_t, file_type, file)
> +corecmd_bin_entry_type(test_restorecon_t)
> +
> +# Allow these for statfs(2) if /tmp = TMPFS_MAGIC test
> +allow test_restorecon_t tmpfs_t:filesystem getattr;
> +allow test_restorecon_t user_tmp_t:sock_file getattr;
> +# and this to add the root test directory
> +allow test_restorecon_t fs_t:filesystem getattr;
> +
> +# Allow restorecon(8) to be used instead of the test program
> +allow test_restorecon_t setfiles_exec_t:file entrypoint;
> +
> +######### No CAP_SYS_ADMIN permission ################
> +
> +type test_no_admin_restorecon_t;
> +files_type(test_no_admin_restorecon_t)
> +
> +domain_type(test_no_admin_restorecon_t)
> +unconfined_runs_test(test_no_admin_restorecon_t)
> +typeattribute test_no_admin_restorecon_t testdomain;
> +typeattribute test_no_admin_restorecon_t restorecon_domain;
> +
> +allow test_no_admin_restorecon_t test_file_t:file relabelfrom;
> +allow test_no_admin_restorecon_t file_type:dir { relabel_dir_perms manage_dir_perms };
> +allow test_no_admin_restorecon_t file_type:file { rw_file_perms execute relabelto relabelfrom };
> +allow_map(test_no_admin_restorecon_t, file_type, file)
> +corecmd_bin_entry_type(test_no_admin_restorecon_t)
> +
> +# and this to add the root test directory
> +allow test_no_admin_restorecon_t fs_t:filesystem getattr;
> +
> +# Allow restorecon(8) to be used instead of the test program
> +allow test_no_admin_restorecon_t setfiles_exec_t:file entrypoint;
> +
> +######################################################
> +# Allow all of these domains to be entered from sysadm domain
> +miscfiles_domain_entry_test_files(restorecon_domain)
> +userdom_sysadm_entry_spec_domtrans_to(restorecon_domain)
> diff --git a/tests/Makefile b/tests/Makefile
> index 63aa325..37ed6af 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -50,6 +50,13 @@ ifeq ($(shell grep "^SELINUX_INFINIBAND_PKEY_TEST=" infiniband_pkey/ibpkey_test.
>   SUBDIRS += infiniband_pkey
>   endif
>   
> +# Keep userspace tests last
> +ifeq ($(TEST_USERSPACE),y)
> +    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
> +        SUBDIRS += restorecon
> +    endif
> +endif
> +
>   ifeq ($(DISTRO),RHEL4)
>       SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
>   endif
> diff --git a/tests/file/test b/tests/file/test
> index 32dd2bd..cb86f41 100755
> --- a/tests/file/test
> +++ b/tests/file/test
> @@ -50,7 +50,7 @@ system "chcon -t fileop_exec_t $basedir/wait_io 2>&1 > /dev/null";
>   # Get the SID of the good file.
>   #
>   $output = `ls -Z $basedir/temp_file`;
> -@arr = split( ' ', $output );
> +@arr    = split( ' ', $output );
>   if ( index( $arr[0], ":" ) != -1 ) {
>       $good_file_sid = $arr[0];
>   }
> diff --git a/tests/restorecon/.gitignore b/tests/restorecon/.gitignore
> new file mode 100644
> index 0000000..98a33e8
> --- /dev/null
> +++ b/tests/restorecon/.gitignore
> @@ -0,0 +1,5 @@
> +restorecon
> +selinux_restorecon_skip
> +restorecon_xattr
> +get_all_digests
> +check_fs
> diff --git a/tests/restorecon/Makefile b/tests/restorecon/Makefile
> new file mode 100644
> index 0000000..477f8d1
> --- /dev/null
> +++ b/tests/restorecon/Makefile
> @@ -0,0 +1,7 @@
> +TARGETS = restorecon selinux_restorecon_skip restorecon_xattr get_all_digests check_fs
> +LDLIBS += -lselinux
> +
> +all: $(TARGETS)
> +
> +clean:
> +	rm -f $(TARGETS)
> diff --git a/tests/restorecon/check_fs.c b/tests/restorecon/check_fs.c
> new file mode 100644
> index 0000000..f18910f
> --- /dev/null
> +++ b/tests/restorecon/check_fs.c
> @@ -0,0 +1,69 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <getopt.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <sys/statfs.h>
> +#include <linux/magic.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-v] <path>\n"
> +		"Where:\n\t"
> +		"-v  Display filesystem f_type magic number.\n\t"
> +		"path  Path to check fs type.\n\n"
> +		"Returns 1=RAMFS_MAGIC, 2=TMPFS_MAGIC, 3=SYSFS_MAGIC\n", progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, rc;
> +	bool verbose = false;
> +	struct statfs sfsb;
> +
> +	while ((opt = getopt(argc, argv, "v")) > 0) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		fprintf(stderr, "No pathname specified\n");
> +		return -1;
> +	}
> +
> +	rc = statfs(argv[optind], &sfsb);
> +	if (rc < 0) {
> +		fprintf(stderr, "Get filesystem statistics ERROR: %s\n",
> +			strerror(errno));
> +		return rc;
> +	}
> +
> +	switch (sfsb.f_type) {
> +	case RAMFS_MAGIC:
> +		if (verbose)
> +			printf("RAMFS_MAGIC\n");
> +		return 1;
> +	case TMPFS_MAGIC:
> +		if (verbose)
> +			printf("TMPFS_MAGIC\n");
> +		return 2;
> +	case SYSFS_MAGIC:
> +		if (verbose)
> +			printf("SYSFS_MAGIC\n");
> +		return 3;
> +	default:
> +		if (verbose)
> +			printf("sfsb.f_type: 0x%lx\n", sfsb.f_type);
> +		return 0;
> +	}
> +
> +	return rc;
> +}
> diff --git a/tests/restorecon/get_all_digests.c b/tests/restorecon/get_all_digests.c
> new file mode 100644
> index 0000000..59f0ed8
> --- /dev/null
> +++ b/tests/restorecon/get_all_digests.c
> @@ -0,0 +1,176 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdint.h>
> +#include <getopt.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <fts.h>
> +#include <selinux/selinux.h>
> +#include <selinux/label.h>
> +
> +#define RESTORECON_PARTIAL_MATCH_DIGEST  "security.sehash"
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-vr] <path>\n\n"
> +		"Where:\n\t"
> +		"-v  Display information.\n\t"
> +		"-r  Recursively descend directories.\n\t"
> +		"path  Path to check current SHA1 digest against file_contexts entries.\n\n"
> +		"This will check the directory selinux.sehash SHA1 digest for "
> +		"<path> against\na newly generated digest based on the "
> +		"file_context entries for that node\n(using the regx, mode "
> +		"and path entries).\n", progname);
> +	exit(1);
> +}
> +
> +static int get_digests(struct selabel_handle *hnd, bool verbose, char *path)
> +{
> +	int rc = 0;
> +	size_t i, digest_len = 0;
> +	bool status;
> +	uint8_t *xattr_digest = NULL;
> +	uint8_t *calculated_digest = NULL;
> +	char *sha1_buf = NULL;
> +
> +	status = selabel_get_digests_all_partial_matches(hnd, path,
> +							 &calculated_digest,
> +							 &xattr_digest,
> +							 &digest_len);
> +
> +	sha1_buf = calloc(1, digest_len * 2 + 1);
> +	if (!sha1_buf) {
> +		fprintf(stderr, "Could not calloc buffer ERROR: %s\n",
> +			strerror(errno));
> +		rc = -1;
> +		goto out;
> +	}
> +
> +	/* rc = 0 NO MATCH, rc = 1 MATCH, rc = 2 NO calculated_digest
> +	 * rc = 4 NO xattr_digest, rc = 6 NO digests
> +	 */
> +	if (status) { /* They match */
> +		if (verbose) {
> +			printf("xattr and file_contexts SHA1 digests match for: %s\n",
> +			       path);
> +
> +			if (calculated_digest) {
> +				for (i = 0; i < digest_len; i++)
> +					sprintf((&sha1_buf[i * 2]), "%02x",
> +						calculated_digest[i]);
> +				printf("SHA1 digest: %s\n", sha1_buf);
> +			}
> +		}
> +
> +		rc = 1;
> +		goto out;
> +	} else {
> +		if (!calculated_digest) {
> +			rc = 2;
> +			if (verbose) {
> +				printf("No SHA1 digest available for: %s\n", path);
> +				printf("as file_context entry is \"<<none>>\"\n");
> +			}
> +		}
> +
> +		if (calculated_digest && verbose) {
> +			printf("The file_context entries for: %s\n", path);
> +
> +			for (i = 0; i < digest_len; i++)
> +				sprintf((&sha1_buf[i * 2]), "%02x", calculated_digest[i]);
> +			printf("generated SHA1 digest: %s\n", sha1_buf);
> +		}
> +		if (!xattr_digest) {
> +			rc = rc | 4;
> +			if (verbose)
> +				printf("however there is no selinux.sehash xattr entry.\n");
> +			else
> +				goto out;
> +
> +		} else if (verbose) {
> +			printf("however it does NOT match the current entry of:\n");
> +			for (i = 0; i < digest_len; i++)
> +				sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
> +			printf("%s\n", sha1_buf);
> +		}
> +	}
> +
> +	free(sha1_buf);
> +out:
> +	free(xattr_digest);
> +	free(calculated_digest);
> +	return rc;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, fts_flags, status;
> +	bool verbose = false, recurse = false;
> +	FTS *fts;
> +	FTSENT *ftsent;
> +	char *paths[2] = { NULL, NULL };
> +	struct selabel_handle *hnd;
> +
> +	if (argc < 2)
> +		usage(argv[0]);
> +
> +	while ((opt = getopt(argc, argv, "vr")) > 0) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		case 'r':
> +			recurse = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		fprintf(stderr, "No pathname specified\n");
> +		exit(-1);
> +	}
> +	paths[0] = argv[optind];
> +
> +	hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
> +	if (!hnd) {
> +		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
> +		return -1;
> +	}
> +
> +	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
> +	fts = fts_open(paths, fts_flags, NULL);
> +	if (!fts) {
> +		printf("fts error on %s: %s\n",
> +		       paths[0], strerror(errno));
> +		return -1;
> +	}
> +
> +	while ((ftsent = fts_read(fts)) != NULL) {
> +		switch (ftsent->fts_info) {
> +		case FTS_D:
> +			/* If recurse = TRUE, then 'status' will reflect the
> +			 * last path match with 0 = NO MATCH, 1 = MATCH,
> +			 * 2 = NO calculated_digest, 4 = NO xattr_digest and
> +			 * 6 = NO digests.
> +			 */
> +			status = get_digests(hnd, verbose, ftsent->fts_path);
> +			if (status < 0)
> +				goto out;
> +			break;
> +		default:
> +			break;
> +		}
> +
> +		if (!recurse)
> +			break;
> +	}
> +
> +out:
> +	(void) fts_close(fts);
> +	(void) selabel_close(hnd);
> +	return status;
> +}
> diff --git a/tests/restorecon/restorecon.c b/tests/restorecon/restorecon.c
> new file mode 100644
> index 0000000..9daa19a
> --- /dev/null
> +++ b/tests/restorecon/restorecon.c
> @@ -0,0 +1,80 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <getopt.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <selinux/selinux.h>
> +#include <selinux/label.h>
> +#include <selinux/restorecon.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-IDrv] <path>\n\n"
> +		"Where:\n\t"
> +		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
> +		"-D  Enable digests\n\t"
> +		"-r  Recursively descend directories.\n\t"
> +		"-v  Display information.\n\t"
> +		"path  Path of file or directory to check.\n\n"
> +		"The parameters must follow those of restorecon(8)\n", progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, rc, flags = 0;
> +	bool request_digest = false;
> +	struct selabel_handle *hnd = NULL;
> +
> +	if (argc < 2)
> +		usage(argv[0]);
> +
> +	while ((opt = getopt(argc, argv, "IDrv")) > 0) {
> +		switch (opt) {
> +		case 'I':
> +			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
> +			request_digest = true;
> +			break;
> +		case 'D':
> +			request_digest = true;
> +			break;
> +		case 'r':
> +			flags |= SELINUX_RESTORECON_RECURSE;
> +			break;
> +		case 'v':
> +			flags |= SELINUX_RESTORECON_VERBOSE;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		fprintf(stderr, "No pathname specified\n");
> +		return -1;
> +	}
> +
> +	struct selinux_opt fc_opts[] = {
> +		{ SELABEL_OPT_DIGEST, (request_digest ? (char *)1 : NULL) }
> +	};
> +
> +	hnd = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
> +	if (!hnd) {
> +		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
> +		return -1;
> +	}
> +
> +	/* Use own handle */
> +	selinux_restorecon_set_sehandle(hnd);
> +
> +	rc = selinux_restorecon(argv[optind], flags);
> +	if (rc < 0)
> +		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
> +			strerror(errno));
> +
> +	selabel_close(hnd);
> +	return rc;
> +}
> +
> diff --git a/tests/restorecon/restorecon_xattr.c b/tests/restorecon/restorecon_xattr.c
> new file mode 100644
> index 0000000..12e89b3
> --- /dev/null
> +++ b/tests/restorecon/restorecon_xattr.c
> @@ -0,0 +1,116 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <getopt.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <selinux/selinux.h>
> +#include <selinux/label.h>
> +#include <selinux/restorecon.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"\nusage: %s [-vrdD] <path>\n"
> +		"\nWhere:\n\t"
> +		"-n  Do not append \"Match\" or \"No Match\" to displayed digests.\n\t"
> +		"-r  Recursively descend directories.\n\t"
> +		"-m  Do not read /proc/mounts for entries to be excluded.\n\t"
> +		"-d  Delete non-matching digest entries.\n\t"
> +		"-D  Delete all digest entries.\n\t"
> +		"path  Path to search for xattr \"security.sehash\" entries.\n\n",
> +		progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, rc;
> +	unsigned int xattr_flags = 0, delete_digest = 0, recurse = 0;
> +	unsigned int delete_all_digests = 0;
> +	struct dir_xattr *current, *next, **xattr_list = NULL;
> +	bool verbose = false;
> +
> +	if (argc < 2)
> +		usage(argv[0]);
> +
> +	while ((opt = getopt(argc, argv, "vrdD")) > 0) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		case 'r':
> +			recurse = SELINUX_RESTORECON_XATTR_RECURSE;
> +			break;
> +		case 'd':
> +			delete_digest =
> +				SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS;
> +			break;
> +		case 'D':
> +			delete_all_digests =
> +				SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		fprintf(stderr, "No pathname specified\n");
> +		exit(-1);
> +	}
> +
> +	xattr_flags = delete_digest | delete_all_digests | recurse;
> +
> +	if (selinux_restorecon_xattr(argv[optind], xattr_flags, &xattr_list)) {
> +		fprintf(stderr, "Error selinux_restorecon_xattr: %s\n",
> +			strerror(errno));
> +		rc = -1;
> +		goto out;
> +	}
> +
> +	if (xattr_list && verbose) {
> +		current = *xattr_list;
> +		while (current) {
> +			next = current->next;
> +			printf("%s ", current->directory);
> +
> +			switch (current->result) {
> +			case MATCH:
> +				printf("Digest: %s Match\n", current->digest);
> +				break;
> +			case NOMATCH:
> +				printf("Digest: %s No Match\n", current->digest);
> +				break;
> +			case DELETED_MATCH:
> +				printf("Deleted Digest: %s Match\n", current->digest);
> +				break;
> +			case DELETED_NOMATCH:
> +				printf("Deleted Digest: %s No Match\n",
> +				       current->digest);
> +				break;
> +			case ERROR:
> +				printf("Digest: %s Error removing xattr\n",
> +				       current->digest);
> +				break;
> +			}
> +			current = next;
> +		}
> +		/* Free memory */
> +		current = *xattr_list;
> +		while (current) {
> +			next = current->next;
> +			free(current->directory);
> +			free(current->digest);
> +			free(current);
> +			current = next;
> +		}
> +	} else if (verbose) {
> +		printf("No digests available\n");
> +	}
> +
> +	rc = 0;
> +out:
> +	return rc;
> +}
> diff --git a/tests/restorecon/selinux_restorecon_skip.c b/tests/restorecon/selinux_restorecon_skip.c
> new file mode 100644
> index 0000000..a09b658
> --- /dev/null
> +++ b/tests/restorecon/selinux_restorecon_skip.c
> @@ -0,0 +1,66 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <getopt.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <selinux/selinux.h>
> +#include <selinux/label.h>
> +#include <selinux/restorecon.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-SIrv] <path>\n\n"
> +		"Where:\n\t"
> +		"-S  set SELINUX_RESTORECON_SKIP_DIGEST\n\t"
> +		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
> +		"-r  Recursively descend directories.\n\t"
> +		"-v  Display information.\n\t"
> +		"path  Path of file or directory to check.\n", progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, rc, flags = 0;
> +
> +	if (argc < 2)
> +		usage(argv[0]);
> +
> +	while ((opt = getopt(argc, argv, "SIrv")) > 0) {
> +		switch (opt) {
> +		case 'I':
> +			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
> +			break;
> +		case 'S':
> +			flags |= SELINUX_RESTORECON_SKIP_DIGEST;
> +			break;
> +		case 'r':
> +			flags |= SELINUX_RESTORECON_RECURSE;
> +			break;
> +		case 'v':
> +			flags |= SELINUX_RESTORECON_VERBOSE;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		fprintf(stderr, "No pathname specified\n");
> +		return -1;
> +	}
> +
> +	/*
> +	 * selinux_restorecon() calls selabel_open(3) and by default enables
> +	 * digests.
> +	 */
> +	rc = selinux_restorecon(argv[optind], flags);
> +	if (rc < 0)
> +		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
> +			strerror(errno));
> +
> +	return rc;
> +}
> +
> diff --git a/tests/restorecon/test b/tests/restorecon/test
> new file mode 100755
> index 0000000..a21765e
> --- /dev/null
> +++ b/tests/restorecon/test
> @@ -0,0 +1,188 @@
> +#!/usr/bin/perl
> +use Test::More;
> +
> +# Options: -v = Verbose, -p <path_to_restorecon>
> +# NOTE: If using the -p option to use a different version of restorecon,
> +# ensure they are labeled correctly before use. This can be achieved by:
> +#    chcon -h -t bin_t .../sbin/restorecon
> +#    chcon -h -t setfiles_exec_t .../sbin/setfiles
> +# The test_restorecon.te policy has rules to support this labeling.
> +
> +BEGIN {
> +    $basedir = $0;
> +    $basedir =~ s|(.*)/[^/]*|$1|;
> +
> +    $v      = " ";
> +    $bindir = $basedir;
> +    $i      = 0;
> +    foreach $arg (@ARGV) {
> +        if ( $arg eq "-v" ) {
> +            $v = $arg;
> +        }
> +        elsif ( $arg eq "-p" ) {
> +            $bindir = $ARGV[ $i + 1 ];
> +            if ( not -e "$bindir/restorecon" ) {
> +                plan skip_all => "$bindir/restorecon not found";
> +            }
> +        }
> +        $i++;
> +    }
> +
> +    # check if /tmp is really type tmpfs (TMPFS_MAGIC).
> +    $test_tmpfs = 0;
> +    $result     = system("$basedir/check_fs $v /tmp 2>/dev/null");
> +    if ( $result >> 8 eq 2 ) {
> +        $test_tmpfs = 1;
> +        plan tests => 12;
> +    }
> +    else {
> +        plan tests => 11;
> +    }
> +}
> +
> +print "Using \"restorecon\" from $bindir\n";
> +
> +# Make sure test directory removed then generate new. Using a root dir to test
> +# libselinux: Ignore the stem when looking up all matches in file context
> +print "Generating test directories\n";
> +system("rm -rf /restore_test");
> +system("mkdir -p /restore_test/in_dir");
> +system("mkdir -p /restore_test/out_dir");
> +
> +# Using semanage is much quicker than using semodule to build fc entries.
> +print "semanage adding file context entries\n";
> +system("semanage fcontext -a -t test_file_t -f d /restore_test");
> +system("semanage fcontext -a -t in_dir_t -f d /restore_test/in_dir");
> +system("semanage fcontext -a -t out_dir_t -f d /restore_test/out_dir");
> +
> +print "Add files to the directories\n";
> +system("touch /restore_test/out_dir/out_file1");
> +system("touch /restore_test/in_dir/in_file1");
> +
> +print "Test no CAP_SYS_ADMIN (setxattr failed)\n";
> +system(
> +    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v /restore_test"
> +);
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +if ( $result >> 8 eq 4 ) {
> +    print "Run selinux_restorecon with digests enabled and no CAP_SYS_ADMIN\n";
> +    system(
> +"runcon -t test_no_admin_restorecon_t $bindir/restorecon -rD $v /restore_test 2>&1"
> +    );
> +    print "Check there are no xattr digest entries\n";
> +    $result = system(
> +"runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test"
> +    );
> +    ok( $result >> 8 eq 4 );
> +}
> +else {
> +    print "Failed no CAP_SYS_ADMIN test\n";
> +    ok(0);
> +}
> +
> +print "Run restorecon to add digest entries, then check they match\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result >> 8 eq 1 );
> +
> +print "Add new file context entries, then check digests do not match\n";
> +system("semanage fcontext -a -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
> +system(
> +    "semanage fcontext -a -t out_file_t -f f \"/restore_test/out_dir(/.*)?\"");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result eq 0 );
> +
> +print "Now fix with restorecon and check digests match\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result >> 8 eq 1 );
> +
> +print "Remove sehash entry on /restore_test/out_dir then check if removed\n";
> +system(
> +"runcon -t test_restorecon_t setfattr -x security.sehash /restore_test/out_dir"
> +);
> +$result = system(
> +"runcon -t test_restorecon_t $basedir/get_all_digests $v /restore_test/out_dir"
> +);
> +ok( $result >> 8 eq 4 );
> +
> +print
> +  "Run restorecon with SELINUX_RESTORECON_IGNORE_DIGEST = TRUE. This will\n";
> +print "rewrite the missing digest, then check they match\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -Ir $v /restore_test");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result >> 8 eq 1 );
> +
> +print "Remove some file context entries, then check digests do not match\n";
> +system("semanage fcontext -d -t in_dir_t -f d /restore_test/in_dir");
> +system("semanage fcontext -d -t out_dir_t -f d /restore_test/out_dir");
> +system("semanage fcontext -d -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result eq 0 );
> +
> +print "Run restorecon with digests disabled, then check digests still do\n";
> +print "not match as they were not updated\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -r $v /restore_test");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result eq 0 );
> +
> +print "Run restorecon with digests enabled, then check they match\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +ok( $result >> 8 eq 1 );
> +
> +print "Test SELINUX_RESTORECON_SKIP_DIGEST\n";
> +system(
> +    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v /restore_test"
> +);
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
> +if ( $result >> 8 eq 4 ) {
> +    print
> +"Run selinux_restorecon with digests enabled and SELINUX_RESTORECON_SKIP_DIGEST = TRUE\n";
> +    system(
> +"runcon -t test_restorecon_t $basedir/selinux_restorecon_skip -rS $v /restore_test"
> +    );
> +
> +    $result = system(
> +"runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test"
> +    );
> +    ok( $result >> 8 eq 4 );
> +}
> +else {
> +    print "Failed SELINUX_RESTORECON_SKIP_DIGEST test\n";
> +    ok(0);
> +}
> +
> +system(
> +    "semanage fcontext -d -t in_file_t -f f \"/restore_test/out_dir(/.*)?\"");
> +system("semanage fcontext -d -t test_file_t -f d /restore_test");
> +system("rm -rf /restore_test");
> +
> +print
> +  "Run restorecon on /sys with digests enabled, then check digests are not\n";
> +print "written as /sys is SYSFS_MAGIC.\n";
> +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /sys/fs/selinux");
> +$result = system(
> +    "runcon -t test_restorecon_t $basedir/get_all_digests $v /sys/fs/selinux");
> +ok( $result >> 8 eq 4 );
> +
> +if ($test_tmpfs) {
> +    print
> +"Run restorecon on /tmp with digests enabled, then check digests are not\n";
> +    print "written as /tmp is TMPFS_MAGIC\n";
> +    system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /tmp");
> +    $result =
> +      system("runcon -t test_restorecon_t $basedir/get_all_digests $v /tmp");
> +    ok( $result >> 8 eq 4 );
> +}
> +
> +exit;
>
Richard Haines Sept. 15, 2019, 2:35 p.m. UTC | #2
On Fri, 2019-09-13 at 13:09 -0400, Stephen Smalley wrote:
> On 6/3/19 5:56 AM, Richard Haines wrote:
> > This will test the updated libselinux selinux_restorecon(3) using a
> > simple test version of "restorecon", or if the test is run locally,
> > a '-p' option can be used to supply the path of a full version of
> > restorecon(8) (see note in restorecon/test).
> > 
> > As the SELinux testsuite is primarily a set of regression tests for
> > the
> > SELinux kernel, this change also adds support to include the
> > testing of
> > core userspace services such as library functions or utilities. To
> > install and run these type of tests, the following must be run
> > first:
> > 
> > 	# export TEST_USERSPACE=y
> 
> Sorry for the long delay in responding. I would prefer userspace
> tests 
> to remain part of the selinux userspace repository, invoked upon
> make 
> test there.  A test policy if required should be possible via a
> separate 
> standalone cil or te/fc policy module that doesn't depend on the 
> testsuite policy.

That's okay as I didn't expect it to be accepted. I just wanted it to
test the restorecon patches that I submitted as I find it more
convenient to write testsuite tests. Maybe one day I'll rewrite for
libselinux.


> 
> > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> > ---
> > V2 Changes:
> > Allow option to run other restorecon binaries.
> > Only run if userspace tests enabled.
> > Add tests for "Ignore the stem..." patch
> > V3 Changes:
> > Add tests for No CAP_SYS_ADMIN permission and SKIP_DIGEST
> > 
> >   README.md                                  |   7 +
> >   policy/Makefile                            |   8 +
> >   policy/test_restorecon.te                  |  74 ++++++++
> >   tests/Makefile                             |   7 +
> >   tests/file/test                            |   2 +-
> >   tests/restorecon/.gitignore                |   5 +
> >   tests/restorecon/Makefile                  |   7 +
> >   tests/restorecon/check_fs.c                |  69 ++++++++
> >   tests/restorecon/get_all_digests.c         | 176
> > +++++++++++++++++++
> >   tests/restorecon/restorecon.c              |  80 +++++++++
> >   tests/restorecon/restorecon_xattr.c        | 116 +++++++++++++
> >   tests/restorecon/selinux_restorecon_skip.c |  66 ++++++++
> >   tests/restorecon/test                      | 188
> > +++++++++++++++++++++
> >   13 files changed, 804 insertions(+), 1 deletion(-)
> >   create mode 100644 policy/test_restorecon.te
> >   create mode 100644 tests/restorecon/.gitignore
> >   create mode 100644 tests/restorecon/Makefile
> >   create mode 100644 tests/restorecon/check_fs.c
> >   create mode 100644 tests/restorecon/get_all_digests.c
> >   create mode 100644 tests/restorecon/restorecon.c
> >   create mode 100644 tests/restorecon/restorecon_xattr.c
> >   create mode 100644 tests/restorecon/selinux_restorecon_skip.c
> >   create mode 100755 tests/restorecon/test
> > 
> > diff --git a/README.md b/README.md
> > index 26784f8..329a495 100644
> > --- a/README.md
> > +++ b/README.md
> > @@ -114,6 +114,13 @@ the tests:
> >   
> >   ## Running the Tests
> >   
> > +The SELinux testsuite is primarily a set of regression tests for
> > the SELinux
> > +kernel, however it is possible to include the testing of core
> > userspace
> > +services such as library functions or utilities. To install any
> > relevant
> > +tests, the following must be run first:
> > +
> > +	# export TEST_USERSPACE=y
> > +
> >   Create a shell with the `unconfined_r` or `sysadm_r` role and the
> > Linux
> >   superuser identity, e.g.:
> >   
> > diff --git a/policy/Makefile b/policy/Makefile
> > index 305b572..9c7d173 100644
> > --- a/policy/Makefile
> > +++ b/policy/Makefile
> > @@ -3,6 +3,7 @@ POLDEV ?= /usr/share/selinux/devel
> >   SEMODULE = /usr/sbin/semodule
> >   CHECKPOLICY = /usr/bin/checkpolicy
> >   CHECKMODULE = /usr/bin/checkmodule
> > +INCLUDEDIR ?= /usr/include
> >   
> >   DISTRO=$(shell ../tests/os_detect)
> >   RHEL_VERS=$(shell echo $(DISTRO) | sed 's/RHEL//')
> > @@ -79,6 +80,13 @@ ifeq (x$(DISTRO),$(filter x$(DISTRO), xRHEL6))
> >   TARGETS:=$(filter-out test_ibpkey.te, $(TARGETS))
> >   endif
> >   
> > +# Add any userspace test policy
> > +ifeq ($(TEST_USERSPACE),y)
> > +    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches
> > $(INCLUDEDIR)/selinux/label.h && echo true),true)
> > +        TARGETS += test_restorecon.te
> > +    endif
> > +endif
> > +
> >   ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5))
> >   	BUILD_TARGET := build_rhel
> >   	LOAD_TARGET := load_rhel
> > diff --git a/policy/test_restorecon.te b/policy/test_restorecon.te
> > new file mode 100644
> > index 0000000..57699bb
> > --- /dev/null
> > +++ b/policy/test_restorecon.te
> > @@ -0,0 +1,74 @@
> > +#################################
> > +#
> > +# Policy for testing restorecon
> > +#
> > +
> > +require {
> > +	attribute file_type;
> > +	type setfiles_exec_t;
> > +}
> > +
> > +attribute restorecon_domain;
> > +
> > +type test_restorecon_file_t;
> > +files_type(test_restorecon_file_t)
> > +type in_dir_t;
> > +files_type(in_dir_t)
> > +type out_dir_t;
> > +files_type(out_dir_t)
> > +type in_file_t;
> > +files_type(in_file_t)
> > +type out_file_t;
> > +files_type(out_file_t)
> > +
> > +# Domain for restorecon.
> > +type test_restorecon_t;
> > +files_type(test_restorecon_t)
> > +
> > +domain_type(test_restorecon_t)
> > +unconfined_runs_test(test_restorecon_t)
> > +typeattribute test_restorecon_t testdomain;
> > +typeattribute test_restorecon_t restorecon_domain;
> > +
> > +allow test_restorecon_t self:capability sys_admin;
> > +allow test_restorecon_t test_file_t:file relabelfrom;
> > +allow test_restorecon_t file_type:dir { relabel_dir_perms
> > manage_dir_perms };
> > +allow test_restorecon_t file_type:file { rw_file_perms execute
> > relabelto relabelfrom };
> > +allow_map(test_restorecon_t, file_type, file)
> > +corecmd_bin_entry_type(test_restorecon_t)
> > +
> > +# Allow these for statfs(2) if /tmp = TMPFS_MAGIC test
> > +allow test_restorecon_t tmpfs_t:filesystem getattr;
> > +allow test_restorecon_t user_tmp_t:sock_file getattr;
> > +# and this to add the root test directory
> > +allow test_restorecon_t fs_t:filesystem getattr;
> > +
> > +# Allow restorecon(8) to be used instead of the test program
> > +allow test_restorecon_t setfiles_exec_t:file entrypoint;
> > +
> > +######### No CAP_SYS_ADMIN permission ################
> > +
> > +type test_no_admin_restorecon_t;
> > +files_type(test_no_admin_restorecon_t)
> > +
> > +domain_type(test_no_admin_restorecon_t)
> > +unconfined_runs_test(test_no_admin_restorecon_t)
> > +typeattribute test_no_admin_restorecon_t testdomain;
> > +typeattribute test_no_admin_restorecon_t restorecon_domain;
> > +
> > +allow test_no_admin_restorecon_t test_file_t:file relabelfrom;
> > +allow test_no_admin_restorecon_t file_type:dir { relabel_dir_perms
> > manage_dir_perms };
> > +allow test_no_admin_restorecon_t file_type:file { rw_file_perms
> > execute relabelto relabelfrom };
> > +allow_map(test_no_admin_restorecon_t, file_type, file)
> > +corecmd_bin_entry_type(test_no_admin_restorecon_t)
> > +
> > +# and this to add the root test directory
> > +allow test_no_admin_restorecon_t fs_t:filesystem getattr;
> > +
> > +# Allow restorecon(8) to be used instead of the test program
> > +allow test_no_admin_restorecon_t setfiles_exec_t:file entrypoint;
> > +
> > +######################################################
> > +# Allow all of these domains to be entered from sysadm domain
> > +miscfiles_domain_entry_test_files(restorecon_domain)
> > +userdom_sysadm_entry_spec_domtrans_to(restorecon_domain)
> > diff --git a/tests/Makefile b/tests/Makefile
> > index 63aa325..37ed6af 100644
> > --- a/tests/Makefile
> > +++ b/tests/Makefile
> > @@ -50,6 +50,13 @@ ifeq ($(shell grep
> > "^SELINUX_INFINIBAND_PKEY_TEST=" infiniband_pkey/ibpkey_test.
> >   SUBDIRS += infiniband_pkey
> >   endif
> >   
> > +# Keep userspace tests last
> > +ifeq ($(TEST_USERSPACE),y)
> > +    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches
> > $(INCLUDEDIR)/selinux/label.h && echo true),true)
> > +        SUBDIRS += restorecon
> > +    endif
> > +endif
> > +
> >   ifeq ($(DISTRO),RHEL4)
> >       SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket
> > mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
> >   endif
> > diff --git a/tests/file/test b/tests/file/test
> > index 32dd2bd..cb86f41 100755
> > --- a/tests/file/test
> > +++ b/tests/file/test
> > @@ -50,7 +50,7 @@ system "chcon -t fileop_exec_t $basedir/wait_io
> > 2>&1 > /dev/null";
> >   # Get the SID of the good file.
> >   #
> >   $output = `ls -Z $basedir/temp_file`;
> > -@arr = split( ' ', $output );
> > +@arr    = split( ' ', $output );
> >   if ( index( $arr[0], ":" ) != -1 ) {
> >       $good_file_sid = $arr[0];
> >   }
> > diff --git a/tests/restorecon/.gitignore
> > b/tests/restorecon/.gitignore
> > new file mode 100644
> > index 0000000..98a33e8
> > --- /dev/null
> > +++ b/tests/restorecon/.gitignore
> > @@ -0,0 +1,5 @@
> > +restorecon
> > +selinux_restorecon_skip
> > +restorecon_xattr
> > +get_all_digests
> > +check_fs
> > diff --git a/tests/restorecon/Makefile b/tests/restorecon/Makefile
> > new file mode 100644
> > index 0000000..477f8d1
> > --- /dev/null
> > +++ b/tests/restorecon/Makefile
> > @@ -0,0 +1,7 @@
> > +TARGETS = restorecon selinux_restorecon_skip restorecon_xattr
> > get_all_digests check_fs
> > +LDLIBS += -lselinux
> > +
> > +all: $(TARGETS)
> > +
> > +clean:
> > +	rm -f $(TARGETS)
> > diff --git a/tests/restorecon/check_fs.c
> > b/tests/restorecon/check_fs.c
> > new file mode 100644
> > index 0000000..f18910f
> > --- /dev/null
> > +++ b/tests/restorecon/check_fs.c
> > @@ -0,0 +1,69 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <sys/statfs.h>
> > +#include <linux/magic.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"usage:  %s [-v] <path>\n"
> > +		"Where:\n\t"
> > +		"-v  Display filesystem f_type magic number.\n\t"
> > +		"path  Path to check fs type.\n\n"
> > +		"Returns 1=RAMFS_MAGIC, 2=TMPFS_MAGIC,
> > 3=SYSFS_MAGIC\n", progname);
> > +	exit(-1);
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, rc;
> > +	bool verbose = false;
> > +	struct statfs sfsb;
> > +
> > +	while ((opt = getopt(argc, argv, "v")) > 0) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc) {
> > +		fprintf(stderr, "No pathname specified\n");
> > +		return -1;
> > +	}
> > +
> > +	rc = statfs(argv[optind], &sfsb);
> > +	if (rc < 0) {
> > +		fprintf(stderr, "Get filesystem statistics ERROR:
> > %s\n",
> > +			strerror(errno));
> > +		return rc;
> > +	}
> > +
> > +	switch (sfsb.f_type) {
> > +	case RAMFS_MAGIC:
> > +		if (verbose)
> > +			printf("RAMFS_MAGIC\n");
> > +		return 1;
> > +	case TMPFS_MAGIC:
> > +		if (verbose)
> > +			printf("TMPFS_MAGIC\n");
> > +		return 2;
> > +	case SYSFS_MAGIC:
> > +		if (verbose)
> > +			printf("SYSFS_MAGIC\n");
> > +		return 3;
> > +	default:
> > +		if (verbose)
> > +			printf("sfsb.f_type: 0x%lx\n", sfsb.f_type);
> > +		return 0;
> > +	}
> > +
> > +	return rc;
> > +}
> > diff --git a/tests/restorecon/get_all_digests.c
> > b/tests/restorecon/get_all_digests.c
> > new file mode 100644
> > index 0000000..59f0ed8
> > --- /dev/null
> > +++ b/tests/restorecon/get_all_digests.c
> > @@ -0,0 +1,176 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <stdint.h>
> > +#include <getopt.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <fts.h>
> > +#include <selinux/selinux.h>
> > +#include <selinux/label.h>
> > +
> > +#define RESTORECON_PARTIAL_MATCH_DIGEST  "security.sehash"
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"usage:  %s [-vr] <path>\n\n"
> > +		"Where:\n\t"
> > +		"-v  Display information.\n\t"
> > +		"-r  Recursively descend directories.\n\t"
> > +		"path  Path to check current SHA1 digest against
> > file_contexts entries.\n\n"
> > +		"This will check the directory selinux.sehash SHA1
> > digest for "
> > +		"<path> against\na newly generated digest based on the
> > "
> > +		"file_context entries for that node\n(using the regx,
> > mode "
> > +		"and path entries).\n", progname);
> > +	exit(1);
> > +}
> > +
> > +static int get_digests(struct selabel_handle *hnd, bool verbose,
> > char *path)
> > +{
> > +	int rc = 0;
> > +	size_t i, digest_len = 0;
> > +	bool status;
> > +	uint8_t *xattr_digest = NULL;
> > +	uint8_t *calculated_digest = NULL;
> > +	char *sha1_buf = NULL;
> > +
> > +	status = selabel_get_digests_all_partial_matches(hnd, path,
> > +							 &calculated_di
> > gest,
> > +							 &xattr_digest,
> > +							 &digest_len);
> > +
> > +	sha1_buf = calloc(1, digest_len * 2 + 1);
> > +	if (!sha1_buf) {
> > +		fprintf(stderr, "Could not calloc buffer ERROR: %s\n",
> > +			strerror(errno));
> > +		rc = -1;
> > +		goto out;
> > +	}
> > +
> > +	/* rc = 0 NO MATCH, rc = 1 MATCH, rc = 2 NO calculated_digest
> > +	 * rc = 4 NO xattr_digest, rc = 6 NO digests
> > +	 */
> > +	if (status) { /* They match */
> > +		if (verbose) {
> > +			printf("xattr and file_contexts SHA1 digests
> > match for: %s\n",
> > +			       path);
> > +
> > +			if (calculated_digest) {
> > +				for (i = 0; i < digest_len; i++)
> > +					sprintf((&sha1_buf[i * 2]),
> > "%02x",
> > +						calculated_digest[i]);
> > +				printf("SHA1 digest: %s\n", sha1_buf);
> > +			}
> > +		}
> > +
> > +		rc = 1;
> > +		goto out;
> > +	} else {
> > +		if (!calculated_digest) {
> > +			rc = 2;
> > +			if (verbose) {
> > +				printf("No SHA1 digest available for:
> > %s\n", path);
> > +				printf("as file_context entry is
> > \"<<none>>\"\n");
> > +			}
> > +		}
> > +
> > +		if (calculated_digest && verbose) {
> > +			printf("The file_context entries for: %s\n",
> > path);
> > +
> > +			for (i = 0; i < digest_len; i++)
> > +				sprintf((&sha1_buf[i * 2]), "%02x",
> > calculated_digest[i]);
> > +			printf("generated SHA1 digest: %s\n",
> > sha1_buf);
> > +		}
> > +		if (!xattr_digest) {
> > +			rc = rc | 4;
> > +			if (verbose)
> > +				printf("however there is no
> > selinux.sehash xattr entry.\n");
> > +			else
> > +				goto out;
> > +
> > +		} else if (verbose) {
> > +			printf("however it does NOT match the current
> > entry of:\n");
> > +			for (i = 0; i < digest_len; i++)
> > +				sprintf((&sha1_buf[i * 2]), "%02x",
> > xattr_digest[i]);
> > +			printf("%s\n", sha1_buf);
> > +		}
> > +	}
> > +
> > +	free(sha1_buf);
> > +out:
> > +	free(xattr_digest);
> > +	free(calculated_digest);
> > +	return rc;
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, fts_flags, status;
> > +	bool verbose = false, recurse = false;
> > +	FTS *fts;
> > +	FTSENT *ftsent;
> > +	char *paths[2] = { NULL, NULL };
> > +	struct selabel_handle *hnd;
> > +
> > +	if (argc < 2)
> > +		usage(argv[0]);
> > +
> > +	while ((opt = getopt(argc, argv, "vr")) > 0) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		case 'r':
> > +			recurse = true;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc) {
> > +		fprintf(stderr, "No pathname specified\n");
> > +		exit(-1);
> > +	}
> > +	paths[0] = argv[optind];
> > +
> > +	hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
> > +	if (!hnd) {
> > +		fprintf(stderr, "ERROR: selabel_open - Could not obtain
> > handle.\n");
> > +		return -1;
> > +	}
> > +
> > +	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
> > +	fts = fts_open(paths, fts_flags, NULL);
> > +	if (!fts) {
> > +		printf("fts error on %s: %s\n",
> > +		       paths[0], strerror(errno));
> > +		return -1;
> > +	}
> > +
> > +	while ((ftsent = fts_read(fts)) != NULL) {
> > +		switch (ftsent->fts_info) {
> > +		case FTS_D:
> > +			/* If recurse = TRUE, then 'status' will
> > reflect the
> > +			 * last path match with 0 = NO MATCH, 1 =
> > MATCH,
> > +			 * 2 = NO calculated_digest, 4 = NO
> > xattr_digest and
> > +			 * 6 = NO digests.
> > +			 */
> > +			status = get_digests(hnd, verbose, ftsent-
> > >fts_path);
> > +			if (status < 0)
> > +				goto out;
> > +			break;
> > +		default:
> > +			break;
> > +		}
> > +
> > +		if (!recurse)
> > +			break;
> > +	}
> > +
> > +out:
> > +	(void) fts_close(fts);
> > +	(void) selabel_close(hnd);
> > +	return status;
> > +}
> > diff --git a/tests/restorecon/restorecon.c
> > b/tests/restorecon/restorecon.c
> > new file mode 100644
> > index 0000000..9daa19a
> > --- /dev/null
> > +++ b/tests/restorecon/restorecon.c
> > @@ -0,0 +1,80 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <selinux/selinux.h>
> > +#include <selinux/label.h>
> > +#include <selinux/restorecon.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"usage:  %s [-IDrv] <path>\n\n"
> > +		"Where:\n\t"
> > +		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
> > +		"-D  Enable digests\n\t"
> > +		"-r  Recursively descend directories.\n\t"
> > +		"-v  Display information.\n\t"
> > +		"path  Path of file or directory to check.\n\n"
> > +		"The parameters must follow those of restorecon(8)\n",
> > progname);
> > +	exit(-1);
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, rc, flags = 0;
> > +	bool request_digest = false;
> > +	struct selabel_handle *hnd = NULL;
> > +
> > +	if (argc < 2)
> > +		usage(argv[0]);
> > +
> > +	while ((opt = getopt(argc, argv, "IDrv")) > 0) {
> > +		switch (opt) {
> > +		case 'I':
> > +			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
> > +			request_digest = true;
> > +			break;
> > +		case 'D':
> > +			request_digest = true;
> > +			break;
> > +		case 'r':
> > +			flags |= SELINUX_RESTORECON_RECURSE;
> > +			break;
> > +		case 'v':
> > +			flags |= SELINUX_RESTORECON_VERBOSE;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc) {
> > +		fprintf(stderr, "No pathname specified\n");
> > +		return -1;
> > +	}
> > +
> > +	struct selinux_opt fc_opts[] = {
> > +		{ SELABEL_OPT_DIGEST, (request_digest ? (char *)1 :
> > NULL) }
> > +	};
> > +
> > +	hnd = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
> > +	if (!hnd) {
> > +		fprintf(stderr, "ERROR: selabel_open - Could not obtain
> > handle.\n");
> > +		return -1;
> > +	}
> > +
> > +	/* Use own handle */
> > +	selinux_restorecon_set_sehandle(hnd);
> > +
> > +	rc = selinux_restorecon(argv[optind], flags);
> > +	if (rc < 0)
> > +		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
> > +			strerror(errno));
> > +
> > +	selabel_close(hnd);
> > +	return rc;
> > +}
> > +
> > diff --git a/tests/restorecon/restorecon_xattr.c
> > b/tests/restorecon/restorecon_xattr.c
> > new file mode 100644
> > index 0000000..12e89b3
> > --- /dev/null
> > +++ b/tests/restorecon/restorecon_xattr.c
> > @@ -0,0 +1,116 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <unistd.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <selinux/selinux.h>
> > +#include <selinux/label.h>
> > +#include <selinux/restorecon.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"\nusage: %s [-vrdD] <path>\n"
> > +		"\nWhere:\n\t"
> > +		"-n  Do not append \"Match\" or \"No Match\" to
> > displayed digests.\n\t"
> > +		"-r  Recursively descend directories.\n\t"
> > +		"-m  Do not read /proc/mounts for entries to be
> > excluded.\n\t"
> > +		"-d  Delete non-matching digest entries.\n\t"
> > +		"-D  Delete all digest entries.\n\t"
> > +		"path  Path to search for xattr \"security.sehash\"
> > entries.\n\n",
> > +		progname);
> > +	exit(-1);
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, rc;
> > +	unsigned int xattr_flags = 0, delete_digest = 0, recurse = 0;
> > +	unsigned int delete_all_digests = 0;
> > +	struct dir_xattr *current, *next, **xattr_list = NULL;
> > +	bool verbose = false;
> > +
> > +	if (argc < 2)
> > +		usage(argv[0]);
> > +
> > +	while ((opt = getopt(argc, argv, "vrdD")) > 0) {
> > +		switch (opt) {
> > +		case 'v':
> > +			verbose = true;
> > +			break;
> > +		case 'r':
> > +			recurse = SELINUX_RESTORECON_XATTR_RECURSE;
> > +			break;
> > +		case 'd':
> > +			delete_digest =
> > +				SELINUX_RESTORECON_XATTR_DELETE_NONMATC
> > H_DIGESTS;
> > +			break;
> > +		case 'D':
> > +			delete_all_digests =
> > +				SELINUX_RESTORECON_XATTR_DELETE_ALL_DIG
> > ESTS;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc) {
> > +		fprintf(stderr, "No pathname specified\n");
> > +		exit(-1);
> > +	}
> > +
> > +	xattr_flags = delete_digest | delete_all_digests | recurse;
> > +
> > +	if (selinux_restorecon_xattr(argv[optind], xattr_flags,
> > &xattr_list)) {
> > +		fprintf(stderr, "Error selinux_restorecon_xattr: %s\n",
> > +			strerror(errno));
> > +		rc = -1;
> > +		goto out;
> > +	}
> > +
> > +	if (xattr_list && verbose) {
> > +		current = *xattr_list;
> > +		while (current) {
> > +			next = current->next;
> > +			printf("%s ", current->directory);
> > +
> > +			switch (current->result) {
> > +			case MATCH:
> > +				printf("Digest: %s Match\n", current-
> > >digest);
> > +				break;
> > +			case NOMATCH:
> > +				printf("Digest: %s No Match\n",
> > current->digest);
> > +				break;
> > +			case DELETED_MATCH:
> > +				printf("Deleted Digest: %s Match\n",
> > current->digest);
> > +				break;
> > +			case DELETED_NOMATCH:
> > +				printf("Deleted Digest: %s No Match\n",
> > +				       current->digest);
> > +				break;
> > +			case ERROR:
> > +				printf("Digest: %s Error removing
> > xattr\n",
> > +				       current->digest);
> > +				break;
> > +			}
> > +			current = next;
> > +		}
> > +		/* Free memory */
> > +		current = *xattr_list;
> > +		while (current) {
> > +			next = current->next;
> > +			free(current->directory);
> > +			free(current->digest);
> > +			free(current);
> > +			current = next;
> > +		}
> > +	} else if (verbose) {
> > +		printf("No digests available\n");
> > +	}
> > +
> > +	rc = 0;
> > +out:
> > +	return rc;
> > +}
> > diff --git a/tests/restorecon/selinux_restorecon_skip.c
> > b/tests/restorecon/selinux_restorecon_skip.c
> > new file mode 100644
> > index 0000000..a09b658
> > --- /dev/null
> > +++ b/tests/restorecon/selinux_restorecon_skip.c
> > @@ -0,0 +1,66 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <errno.h>
> > +#include <stdbool.h>
> > +#include <selinux/selinux.h>
> > +#include <selinux/label.h>
> > +#include <selinux/restorecon.h>
> > +
> > +static void usage(char *progname)
> > +{
> > +	fprintf(stderr,
> > +		"usage:  %s [-SIrv] <path>\n\n"
> > +		"Where:\n\t"
> > +		"-S  set SELINUX_RESTORECON_SKIP_DIGEST\n\t"
> > +		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
> > +		"-r  Recursively descend directories.\n\t"
> > +		"-v  Display information.\n\t"
> > +		"path  Path of file or directory to check.\n",
> > progname);
> > +	exit(-1);
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int opt, rc, flags = 0;
> > +
> > +	if (argc < 2)
> > +		usage(argv[0]);
> > +
> > +	while ((opt = getopt(argc, argv, "SIrv")) > 0) {
> > +		switch (opt) {
> > +		case 'I':
> > +			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
> > +			break;
> > +		case 'S':
> > +			flags |= SELINUX_RESTORECON_SKIP_DIGEST;
> > +			break;
> > +		case 'r':
> > +			flags |= SELINUX_RESTORECON_RECURSE;
> > +			break;
> > +		case 'v':
> > +			flags |= SELINUX_RESTORECON_VERBOSE;
> > +			break;
> > +		default:
> > +			usage(argv[0]);
> > +		}
> > +	}
> > +
> > +	if (optind >= argc) {
> > +		fprintf(stderr, "No pathname specified\n");
> > +		return -1;
> > +	}
> > +
> > +	/*
> > +	 * selinux_restorecon() calls selabel_open(3) and by default
> > enables
> > +	 * digests.
> > +	 */
> > +	rc = selinux_restorecon(argv[optind], flags);
> > +	if (rc < 0)
> > +		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
> > +			strerror(errno));
> > +
> > +	return rc;
> > +}
> > +
> > diff --git a/tests/restorecon/test b/tests/restorecon/test
> > new file mode 100755
> > index 0000000..a21765e
> > --- /dev/null
> > +++ b/tests/restorecon/test
> > @@ -0,0 +1,188 @@
> > +#!/usr/bin/perl
> > +use Test::More;
> > +
> > +# Options: -v = Verbose, -p <path_to_restorecon>
> > +# NOTE: If using the -p option to use a different version of
> > restorecon,
> > +# ensure they are labeled correctly before use. This can be
> > achieved by:
> > +#    chcon -h -t bin_t .../sbin/restorecon
> > +#    chcon -h -t setfiles_exec_t .../sbin/setfiles
> > +# The test_restorecon.te policy has rules to support this
> > labeling.
> > +
> > +BEGIN {
> > +    $basedir = $0;
> > +    $basedir =~ s|(.*)/[^/]*|$1|;
> > +
> > +    $v      = " ";
> > +    $bindir = $basedir;
> > +    $i      = 0;
> > +    foreach $arg (@ARGV) {
> > +        if ( $arg eq "-v" ) {
> > +            $v = $arg;
> > +        }
> > +        elsif ( $arg eq "-p" ) {
> > +            $bindir = $ARGV[ $i + 1 ];
> > +            if ( not -e "$bindir/restorecon" ) {
> > +                plan skip_all => "$bindir/restorecon not found";
> > +            }
> > +        }
> > +        $i++;
> > +    }
> > +
> > +    # check if /tmp is really type tmpfs (TMPFS_MAGIC).
> > +    $test_tmpfs = 0;
> > +    $result     = system("$basedir/check_fs $v /tmp 2>/dev/null");
> > +    if ( $result >> 8 eq 2 ) {
> > +        $test_tmpfs = 1;
> > +        plan tests => 12;
> > +    }
> > +    else {
> > +        plan tests => 11;
> > +    }
> > +}
> > +
> > +print "Using \"restorecon\" from $bindir\n";
> > +
> > +# Make sure test directory removed then generate new. Using a root
> > dir to test
> > +# libselinux: Ignore the stem when looking up all matches in file
> > context
> > +print "Generating test directories\n";
> > +system("rm -rf /restore_test");
> > +system("mkdir -p /restore_test/in_dir");
> > +system("mkdir -p /restore_test/out_dir");
> > +
> > +# Using semanage is much quicker than using semodule to build fc
> > entries.
> > +print "semanage adding file context entries\n";
> > +system("semanage fcontext -a -t test_file_t -f d /restore_test");
> > +system("semanage fcontext -a -t in_dir_t -f d
> > /restore_test/in_dir");
> > +system("semanage fcontext -a -t out_dir_t -f d
> > /restore_test/out_dir");
> > +
> > +print "Add files to the directories\n";
> > +system("touch /restore_test/out_dir/out_file1");
> > +system("touch /restore_test/in_dir/in_file1");
> > +
> > +print "Test no CAP_SYS_ADMIN (setxattr failed)\n";
> > +system(
> > +    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v
> > /restore_test"
> > +);
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +if ( $result >> 8 eq 4 ) {
> > +    print "Run selinux_restorecon with digests enabled and no
> > CAP_SYS_ADMIN\n";
> > +    system(
> > +"runcon -t test_no_admin_restorecon_t $bindir/restorecon -rD $v
> > /restore_test 2>&1"
> > +    );
> > +    print "Check there are no xattr digest entries\n";
> > +    $result = system(
> > +"runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test"
> > +    );
> > +    ok( $result >> 8 eq 4 );
> > +}
> > +else {
> > +    print "Failed no CAP_SYS_ADMIN test\n";
> > +    ok(0);
> > +}
> > +
> > +print "Run restorecon to add digest entries, then check they
> > match\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v
> > /restore_test");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result >> 8 eq 1 );
> > +
> > +print "Add new file context entries, then check digests do not
> > match\n";
> > +system("semanage fcontext -a -t in_file_t -f f
> > \"/restore_test/in_dir(/.*)?\"");
> > +system(
> > +    "semanage fcontext -a -t out_file_t -f f
> > \"/restore_test/out_dir(/.*)?\"");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result eq 0 );
> > +
> > +print "Now fix with restorecon and check digests match\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v
> > /restore_test");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result >> 8 eq 1 );
> > +
> > +print "Remove sehash entry on /restore_test/out_dir then check if
> > removed\n";
> > +system(
> > +"runcon -t test_restorecon_t setfattr -x security.sehash
> > /restore_test/out_dir"
> > +);
> > +$result = system(
> > +"runcon -t test_restorecon_t $basedir/get_all_digests $v
> > /restore_test/out_dir"
> > +);
> > +ok( $result >> 8 eq 4 );
> > +
> > +print
> > +  "Run restorecon with SELINUX_RESTORECON_IGNORE_DIGEST = TRUE.
> > This will\n";
> > +print "rewrite the missing digest, then check they match\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -Ir $v
> > /restore_test");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result >> 8 eq 1 );
> > +
> > +print "Remove some file context entries, then check digests do not
> > match\n";
> > +system("semanage fcontext -d -t in_dir_t -f d
> > /restore_test/in_dir");
> > +system("semanage fcontext -d -t out_dir_t -f d
> > /restore_test/out_dir");
> > +system("semanage fcontext -d -t in_file_t -f f
> > \"/restore_test/in_dir(/.*)?\"");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result eq 0 );
> > +
> > +print "Run restorecon with digests disabled, then check digests
> > still do\n";
> > +print "not match as they were not updated\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -r $v
> > /restore_test");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result eq 0 );
> > +
> > +print "Run restorecon with digests enabled, then check they
> > match\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v
> > /restore_test");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +ok( $result >> 8 eq 1 );
> > +
> > +print "Test SELINUX_RESTORECON_SKIP_DIGEST\n";
> > +system(
> > +    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v
> > /restore_test"
> > +);
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test");
> > +if ( $result >> 8 eq 4 ) {
> > +    print
> > +"Run selinux_restorecon with digests enabled and
> > SELINUX_RESTORECON_SKIP_DIGEST = TRUE\n";
> > +    system(
> > +"runcon -t test_restorecon_t $basedir/selinux_restorecon_skip -rS
> > $v /restore_test"
> > +    );
> > +
> > +    $result = system(
> > +"runcon -t test_restorecon_t $basedir/get_all_digests -r $v
> > /restore_test"
> > +    );
> > +    ok( $result >> 8 eq 4 );
> > +}
> > +else {
> > +    print "Failed SELINUX_RESTORECON_SKIP_DIGEST test\n";
> > +    ok(0);
> > +}
> > +
> > +system(
> > +    "semanage fcontext -d -t in_file_t -f f
> > \"/restore_test/out_dir(/.*)?\"");
> > +system("semanage fcontext -d -t test_file_t -f d /restore_test");
> > +system("rm -rf /restore_test");
> > +
> > +print
> > +  "Run restorecon on /sys with digests enabled, then check digests
> > are not\n";
> > +print "written as /sys is SYSFS_MAGIC.\n";
> > +system("runcon -t test_restorecon_t $bindir/restorecon -rD $v
> > /sys/fs/selinux");
> > +$result = system(
> > +    "runcon -t test_restorecon_t $basedir/get_all_digests $v
> > /sys/fs/selinux");
> > +ok( $result >> 8 eq 4 );
> > +
> > +if ($test_tmpfs) {
> > +    print
> > +"Run restorecon on /tmp with digests enabled, then check digests
> > are not\n";
> > +    print "written as /tmp is TMPFS_MAGIC\n";
> > +    system("runcon -t test_restorecon_t $bindir/restorecon -rD $v
> > /tmp");
> > +    $result =
> > +      system("runcon -t test_restorecon_t $basedir/get_all_digests
> > $v /tmp");
> > +    ok( $result >> 8 eq 4 );
> > +}
> > +
> > +exit;
> >

Patch
diff mbox series

diff --git a/README.md b/README.md
index 26784f8..329a495 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,13 @@  the tests:
 
 ## Running the Tests
 
+The SELinux testsuite is primarily a set of regression tests for the SELinux
+kernel, however it is possible to include the testing of core userspace
+services such as library functions or utilities. To install any relevant
+tests, the following must be run first:
+
+	# export TEST_USERSPACE=y
+
 Create a shell with the `unconfined_r` or `sysadm_r` role and the Linux
 superuser identity, e.g.:
 
diff --git a/policy/Makefile b/policy/Makefile
index 305b572..9c7d173 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -3,6 +3,7 @@  POLDEV ?= /usr/share/selinux/devel
 SEMODULE = /usr/sbin/semodule
 CHECKPOLICY = /usr/bin/checkpolicy
 CHECKMODULE = /usr/bin/checkmodule
+INCLUDEDIR ?= /usr/include
 
 DISTRO=$(shell ../tests/os_detect)
 RHEL_VERS=$(shell echo $(DISTRO) | sed 's/RHEL//')
@@ -79,6 +80,13 @@  ifeq (x$(DISTRO),$(filter x$(DISTRO), xRHEL6))
 TARGETS:=$(filter-out test_ibpkey.te, $(TARGETS))
 endif
 
+# Add any userspace test policy
+ifeq ($(TEST_USERSPACE),y)
+    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
+        TARGETS += test_restorecon.te
+    endif
+endif
+
 ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5))
 	BUILD_TARGET := build_rhel
 	LOAD_TARGET := load_rhel
diff --git a/policy/test_restorecon.te b/policy/test_restorecon.te
new file mode 100644
index 0000000..57699bb
--- /dev/null
+++ b/policy/test_restorecon.te
@@ -0,0 +1,74 @@ 
+#################################
+#
+# Policy for testing restorecon
+#
+
+require {
+	attribute file_type;
+	type setfiles_exec_t;
+}
+
+attribute restorecon_domain;
+
+type test_restorecon_file_t;
+files_type(test_restorecon_file_t)
+type in_dir_t;
+files_type(in_dir_t)
+type out_dir_t;
+files_type(out_dir_t)
+type in_file_t;
+files_type(in_file_t)
+type out_file_t;
+files_type(out_file_t)
+
+# Domain for restorecon.
+type test_restorecon_t;
+files_type(test_restorecon_t)
+
+domain_type(test_restorecon_t)
+unconfined_runs_test(test_restorecon_t)
+typeattribute test_restorecon_t testdomain;
+typeattribute test_restorecon_t restorecon_domain;
+
+allow test_restorecon_t self:capability sys_admin;
+allow test_restorecon_t test_file_t:file relabelfrom;
+allow test_restorecon_t file_type:dir { relabel_dir_perms manage_dir_perms };
+allow test_restorecon_t file_type:file { rw_file_perms execute relabelto relabelfrom };
+allow_map(test_restorecon_t, file_type, file)
+corecmd_bin_entry_type(test_restorecon_t)
+
+# Allow these for statfs(2) if /tmp = TMPFS_MAGIC test
+allow test_restorecon_t tmpfs_t:filesystem getattr;
+allow test_restorecon_t user_tmp_t:sock_file getattr;
+# and this to add the root test directory
+allow test_restorecon_t fs_t:filesystem getattr;
+
+# Allow restorecon(8) to be used instead of the test program
+allow test_restorecon_t setfiles_exec_t:file entrypoint;
+
+######### No CAP_SYS_ADMIN permission ################
+
+type test_no_admin_restorecon_t;
+files_type(test_no_admin_restorecon_t)
+
+domain_type(test_no_admin_restorecon_t)
+unconfined_runs_test(test_no_admin_restorecon_t)
+typeattribute test_no_admin_restorecon_t testdomain;
+typeattribute test_no_admin_restorecon_t restorecon_domain;
+
+allow test_no_admin_restorecon_t test_file_t:file relabelfrom;
+allow test_no_admin_restorecon_t file_type:dir { relabel_dir_perms manage_dir_perms };
+allow test_no_admin_restorecon_t file_type:file { rw_file_perms execute relabelto relabelfrom };
+allow_map(test_no_admin_restorecon_t, file_type, file)
+corecmd_bin_entry_type(test_no_admin_restorecon_t)
+
+# and this to add the root test directory
+allow test_no_admin_restorecon_t fs_t:filesystem getattr;
+
+# Allow restorecon(8) to be used instead of the test program
+allow test_no_admin_restorecon_t setfiles_exec_t:file entrypoint;
+
+######################################################
+# Allow all of these domains to be entered from sysadm domain
+miscfiles_domain_entry_test_files(restorecon_domain)
+userdom_sysadm_entry_spec_domtrans_to(restorecon_domain)
diff --git a/tests/Makefile b/tests/Makefile
index 63aa325..37ed6af 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -50,6 +50,13 @@  ifeq ($(shell grep "^SELINUX_INFINIBAND_PKEY_TEST=" infiniband_pkey/ibpkey_test.
 SUBDIRS += infiniband_pkey
 endif
 
+# Keep userspace tests last
+ifeq ($(TEST_USERSPACE),y)
+    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
+        SUBDIRS += restorecon
+    endif
+endif
+
 ifeq ($(DISTRO),RHEL4)
     SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
 endif
diff --git a/tests/file/test b/tests/file/test
index 32dd2bd..cb86f41 100755
--- a/tests/file/test
+++ b/tests/file/test
@@ -50,7 +50,7 @@  system "chcon -t fileop_exec_t $basedir/wait_io 2>&1 > /dev/null";
 # Get the SID of the good file.
 #
 $output = `ls -Z $basedir/temp_file`;
-@arr = split( ' ', $output );
+@arr    = split( ' ', $output );
 if ( index( $arr[0], ":" ) != -1 ) {
     $good_file_sid = $arr[0];
 }
diff --git a/tests/restorecon/.gitignore b/tests/restorecon/.gitignore
new file mode 100644
index 0000000..98a33e8
--- /dev/null
+++ b/tests/restorecon/.gitignore
@@ -0,0 +1,5 @@ 
+restorecon
+selinux_restorecon_skip
+restorecon_xattr
+get_all_digests
+check_fs
diff --git a/tests/restorecon/Makefile b/tests/restorecon/Makefile
new file mode 100644
index 0000000..477f8d1
--- /dev/null
+++ b/tests/restorecon/Makefile
@@ -0,0 +1,7 @@ 
+TARGETS = restorecon selinux_restorecon_skip restorecon_xattr get_all_digests check_fs
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/restorecon/check_fs.c b/tests/restorecon/check_fs.c
new file mode 100644
index 0000000..f18910f
--- /dev/null
+++ b/tests/restorecon/check_fs.c
@@ -0,0 +1,69 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/statfs.h>
+#include <linux/magic.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-v] <path>\n"
+		"Where:\n\t"
+		"-v  Display filesystem f_type magic number.\n\t"
+		"path  Path to check fs type.\n\n"
+		"Returns 1=RAMFS_MAGIC, 2=TMPFS_MAGIC, 3=SYSFS_MAGIC\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc;
+	bool verbose = false;
+	struct statfs sfsb;
+
+	while ((opt = getopt(argc, argv, "v")) > 0) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		return -1;
+	}
+
+	rc = statfs(argv[optind], &sfsb);
+	if (rc < 0) {
+		fprintf(stderr, "Get filesystem statistics ERROR: %s\n",
+			strerror(errno));
+		return rc;
+	}
+
+	switch (sfsb.f_type) {
+	case RAMFS_MAGIC:
+		if (verbose)
+			printf("RAMFS_MAGIC\n");
+		return 1;
+	case TMPFS_MAGIC:
+		if (verbose)
+			printf("TMPFS_MAGIC\n");
+		return 2;
+	case SYSFS_MAGIC:
+		if (verbose)
+			printf("SYSFS_MAGIC\n");
+		return 3;
+	default:
+		if (verbose)
+			printf("sfsb.f_type: 0x%lx\n", sfsb.f_type);
+		return 0;
+	}
+
+	return rc;
+}
diff --git a/tests/restorecon/get_all_digests.c b/tests/restorecon/get_all_digests.c
new file mode 100644
index 0000000..59f0ed8
--- /dev/null
+++ b/tests/restorecon/get_all_digests.c
@@ -0,0 +1,176 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <fts.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+#define RESTORECON_PARTIAL_MATCH_DIGEST  "security.sehash"
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-vr] <path>\n\n"
+		"Where:\n\t"
+		"-v  Display information.\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"path  Path to check current SHA1 digest against file_contexts entries.\n\n"
+		"This will check the directory selinux.sehash SHA1 digest for "
+		"<path> against\na newly generated digest based on the "
+		"file_context entries for that node\n(using the regx, mode "
+		"and path entries).\n", progname);
+	exit(1);
+}
+
+static int get_digests(struct selabel_handle *hnd, bool verbose, char *path)
+{
+	int rc = 0;
+	size_t i, digest_len = 0;
+	bool status;
+	uint8_t *xattr_digest = NULL;
+	uint8_t *calculated_digest = NULL;
+	char *sha1_buf = NULL;
+
+	status = selabel_get_digests_all_partial_matches(hnd, path,
+							 &calculated_digest,
+							 &xattr_digest,
+							 &digest_len);
+
+	sha1_buf = calloc(1, digest_len * 2 + 1);
+	if (!sha1_buf) {
+		fprintf(stderr, "Could not calloc buffer ERROR: %s\n",
+			strerror(errno));
+		rc = -1;
+		goto out;
+	}
+
+	/* rc = 0 NO MATCH, rc = 1 MATCH, rc = 2 NO calculated_digest
+	 * rc = 4 NO xattr_digest, rc = 6 NO digests
+	 */
+	if (status) { /* They match */
+		if (verbose) {
+			printf("xattr and file_contexts SHA1 digests match for: %s\n",
+			       path);
+
+			if (calculated_digest) {
+				for (i = 0; i < digest_len; i++)
+					sprintf((&sha1_buf[i * 2]), "%02x",
+						calculated_digest[i]);
+				printf("SHA1 digest: %s\n", sha1_buf);
+			}
+		}
+
+		rc = 1;
+		goto out;
+	} else {
+		if (!calculated_digest) {
+			rc = 2;
+			if (verbose) {
+				printf("No SHA1 digest available for: %s\n", path);
+				printf("as file_context entry is \"<<none>>\"\n");
+			}
+		}
+
+		if (calculated_digest && verbose) {
+			printf("The file_context entries for: %s\n", path);
+
+			for (i = 0; i < digest_len; i++)
+				sprintf((&sha1_buf[i * 2]), "%02x", calculated_digest[i]);
+			printf("generated SHA1 digest: %s\n", sha1_buf);
+		}
+		if (!xattr_digest) {
+			rc = rc | 4;
+			if (verbose)
+				printf("however there is no selinux.sehash xattr entry.\n");
+			else
+				goto out;
+
+		} else if (verbose) {
+			printf("however it does NOT match the current entry of:\n");
+			for (i = 0; i < digest_len; i++)
+				sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
+			printf("%s\n", sha1_buf);
+		}
+	}
+
+	free(sha1_buf);
+out:
+	free(xattr_digest);
+	free(calculated_digest);
+	return rc;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, fts_flags, status;
+	bool verbose = false, recurse = false;
+	FTS *fts;
+	FTSENT *ftsent;
+	char *paths[2] = { NULL, NULL };
+	struct selabel_handle *hnd;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "vr")) > 0) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		case 'r':
+			recurse = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		exit(-1);
+	}
+	paths[0] = argv[optind];
+
+	hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+	if (!hnd) {
+		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
+		return -1;
+	}
+
+	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
+	fts = fts_open(paths, fts_flags, NULL);
+	if (!fts) {
+		printf("fts error on %s: %s\n",
+		       paths[0], strerror(errno));
+		return -1;
+	}
+
+	while ((ftsent = fts_read(fts)) != NULL) {
+		switch (ftsent->fts_info) {
+		case FTS_D:
+			/* If recurse = TRUE, then 'status' will reflect the
+			 * last path match with 0 = NO MATCH, 1 = MATCH,
+			 * 2 = NO calculated_digest, 4 = NO xattr_digest and
+			 * 6 = NO digests.
+			 */
+			status = get_digests(hnd, verbose, ftsent->fts_path);
+			if (status < 0)
+				goto out;
+			break;
+		default:
+			break;
+		}
+
+		if (!recurse)
+			break;
+	}
+
+out:
+	(void) fts_close(fts);
+	(void) selabel_close(hnd);
+	return status;
+}
diff --git a/tests/restorecon/restorecon.c b/tests/restorecon/restorecon.c
new file mode 100644
index 0000000..9daa19a
--- /dev/null
+++ b/tests/restorecon/restorecon.c
@@ -0,0 +1,80 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <selinux/restorecon.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-IDrv] <path>\n\n"
+		"Where:\n\t"
+		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
+		"-D  Enable digests\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"-v  Display information.\n\t"
+		"path  Path of file or directory to check.\n\n"
+		"The parameters must follow those of restorecon(8)\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc, flags = 0;
+	bool request_digest = false;
+	struct selabel_handle *hnd = NULL;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "IDrv")) > 0) {
+		switch (opt) {
+		case 'I':
+			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
+			request_digest = true;
+			break;
+		case 'D':
+			request_digest = true;
+			break;
+		case 'r':
+			flags |= SELINUX_RESTORECON_RECURSE;
+			break;
+		case 'v':
+			flags |= SELINUX_RESTORECON_VERBOSE;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		return -1;
+	}
+
+	struct selinux_opt fc_opts[] = {
+		{ SELABEL_OPT_DIGEST, (request_digest ? (char *)1 : NULL) }
+	};
+
+	hnd = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
+	if (!hnd) {
+		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
+		return -1;
+	}
+
+	/* Use own handle */
+	selinux_restorecon_set_sehandle(hnd);
+
+	rc = selinux_restorecon(argv[optind], flags);
+	if (rc < 0)
+		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
+			strerror(errno));
+
+	selabel_close(hnd);
+	return rc;
+}
+
diff --git a/tests/restorecon/restorecon_xattr.c b/tests/restorecon/restorecon_xattr.c
new file mode 100644
index 0000000..12e89b3
--- /dev/null
+++ b/tests/restorecon/restorecon_xattr.c
@@ -0,0 +1,116 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <selinux/restorecon.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"\nusage: %s [-vrdD] <path>\n"
+		"\nWhere:\n\t"
+		"-n  Do not append \"Match\" or \"No Match\" to displayed digests.\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"-m  Do not read /proc/mounts for entries to be excluded.\n\t"
+		"-d  Delete non-matching digest entries.\n\t"
+		"-D  Delete all digest entries.\n\t"
+		"path  Path to search for xattr \"security.sehash\" entries.\n\n",
+		progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc;
+	unsigned int xattr_flags = 0, delete_digest = 0, recurse = 0;
+	unsigned int delete_all_digests = 0;
+	struct dir_xattr *current, *next, **xattr_list = NULL;
+	bool verbose = false;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "vrdD")) > 0) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		case 'r':
+			recurse = SELINUX_RESTORECON_XATTR_RECURSE;
+			break;
+		case 'd':
+			delete_digest =
+				SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS;
+			break;
+		case 'D':
+			delete_all_digests =
+				SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		exit(-1);
+	}
+
+	xattr_flags = delete_digest | delete_all_digests | recurse;
+
+	if (selinux_restorecon_xattr(argv[optind], xattr_flags, &xattr_list)) {
+		fprintf(stderr, "Error selinux_restorecon_xattr: %s\n",
+			strerror(errno));
+		rc = -1;
+		goto out;
+	}
+
+	if (xattr_list && verbose) {
+		current = *xattr_list;
+		while (current) {
+			next = current->next;
+			printf("%s ", current->directory);
+
+			switch (current->result) {
+			case MATCH:
+				printf("Digest: %s Match\n", current->digest);
+				break;
+			case NOMATCH:
+				printf("Digest: %s No Match\n", current->digest);
+				break;
+			case DELETED_MATCH:
+				printf("Deleted Digest: %s Match\n", current->digest);
+				break;
+			case DELETED_NOMATCH:
+				printf("Deleted Digest: %s No Match\n",
+				       current->digest);
+				break;
+			case ERROR:
+				printf("Digest: %s Error removing xattr\n",
+				       current->digest);
+				break;
+			}
+			current = next;
+		}
+		/* Free memory */
+		current = *xattr_list;
+		while (current) {
+			next = current->next;
+			free(current->directory);
+			free(current->digest);
+			free(current);
+			current = next;
+		}
+	} else if (verbose) {
+		printf("No digests available\n");
+	}
+
+	rc = 0;
+out:
+	return rc;
+}
diff --git a/tests/restorecon/selinux_restorecon_skip.c b/tests/restorecon/selinux_restorecon_skip.c
new file mode 100644
index 0000000..a09b658
--- /dev/null
+++ b/tests/restorecon/selinux_restorecon_skip.c
@@ -0,0 +1,66 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <selinux/restorecon.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-SIrv] <path>\n\n"
+		"Where:\n\t"
+		"-S  set SELINUX_RESTORECON_SKIP_DIGEST\n\t"
+		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"-v  Display information.\n\t"
+		"path  Path of file or directory to check.\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc, flags = 0;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "SIrv")) > 0) {
+		switch (opt) {
+		case 'I':
+			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
+			break;
+		case 'S':
+			flags |= SELINUX_RESTORECON_SKIP_DIGEST;
+			break;
+		case 'r':
+			flags |= SELINUX_RESTORECON_RECURSE;
+			break;
+		case 'v':
+			flags |= SELINUX_RESTORECON_VERBOSE;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		return -1;
+	}
+
+	/*
+	 * selinux_restorecon() calls selabel_open(3) and by default enables
+	 * digests.
+	 */
+	rc = selinux_restorecon(argv[optind], flags);
+	if (rc < 0)
+		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
+			strerror(errno));
+
+	return rc;
+}
+
diff --git a/tests/restorecon/test b/tests/restorecon/test
new file mode 100755
index 0000000..a21765e
--- /dev/null
+++ b/tests/restorecon/test
@@ -0,0 +1,188 @@ 
+#!/usr/bin/perl
+use Test::More;
+
+# Options: -v = Verbose, -p <path_to_restorecon>
+# NOTE: If using the -p option to use a different version of restorecon,
+# ensure they are labeled correctly before use. This can be achieved by:
+#    chcon -h -t bin_t .../sbin/restorecon
+#    chcon -h -t setfiles_exec_t .../sbin/setfiles
+# The test_restorecon.te policy has rules to support this labeling.
+
+BEGIN {
+    $basedir = $0;
+    $basedir =~ s|(.*)/[^/]*|$1|;
+
+    $v      = " ";
+    $bindir = $basedir;
+    $i      = 0;
+    foreach $arg (@ARGV) {
+        if ( $arg eq "-v" ) {
+            $v = $arg;
+        }
+        elsif ( $arg eq "-p" ) {
+            $bindir = $ARGV[ $i + 1 ];
+            if ( not -e "$bindir/restorecon" ) {
+                plan skip_all => "$bindir/restorecon not found";
+            }
+        }
+        $i++;
+    }
+
+    # check if /tmp is really type tmpfs (TMPFS_MAGIC).
+    $test_tmpfs = 0;
+    $result     = system("$basedir/check_fs $v /tmp 2>/dev/null");
+    if ( $result >> 8 eq 2 ) {
+        $test_tmpfs = 1;
+        plan tests => 12;
+    }
+    else {
+        plan tests => 11;
+    }
+}
+
+print "Using \"restorecon\" from $bindir\n";
+
+# Make sure test directory removed then generate new. Using a root dir to test
+# libselinux: Ignore the stem when looking up all matches in file context
+print "Generating test directories\n";
+system("rm -rf /restore_test");
+system("mkdir -p /restore_test/in_dir");
+system("mkdir -p /restore_test/out_dir");
+
+# Using semanage is much quicker than using semodule to build fc entries.
+print "semanage adding file context entries\n";
+system("semanage fcontext -a -t test_file_t -f d /restore_test");
+system("semanage fcontext -a -t in_dir_t -f d /restore_test/in_dir");
+system("semanage fcontext -a -t out_dir_t -f d /restore_test/out_dir");
+
+print "Add files to the directories\n";
+system("touch /restore_test/out_dir/out_file1");
+system("touch /restore_test/in_dir/in_file1");
+
+print "Test no CAP_SYS_ADMIN (setxattr failed)\n";
+system(
+    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v /restore_test"
+);
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+if ( $result >> 8 eq 4 ) {
+    print "Run selinux_restorecon with digests enabled and no CAP_SYS_ADMIN\n";
+    system(
+"runcon -t test_no_admin_restorecon_t $bindir/restorecon -rD $v /restore_test 2>&1"
+    );
+    print "Check there are no xattr digest entries\n";
+    $result = system(
+"runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test"
+    );
+    ok( $result >> 8 eq 4 );
+}
+else {
+    print "Failed no CAP_SYS_ADMIN test\n";
+    ok(0);
+}
+
+print "Run restorecon to add digest entries, then check they match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Add new file context entries, then check digests do not match\n";
+system("semanage fcontext -a -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
+system(
+    "semanage fcontext -a -t out_file_t -f f \"/restore_test/out_dir(/.*)?\"");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Now fix with restorecon and check digests match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Remove sehash entry on /restore_test/out_dir then check if removed\n";
+system(
+"runcon -t test_restorecon_t setfattr -x security.sehash /restore_test/out_dir"
+);
+$result = system(
+"runcon -t test_restorecon_t $basedir/get_all_digests $v /restore_test/out_dir"
+);
+ok( $result >> 8 eq 4 );
+
+print
+  "Run restorecon with SELINUX_RESTORECON_IGNORE_DIGEST = TRUE. This will\n";
+print "rewrite the missing digest, then check they match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -Ir $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Remove some file context entries, then check digests do not match\n";
+system("semanage fcontext -d -t in_dir_t -f d /restore_test/in_dir");
+system("semanage fcontext -d -t out_dir_t -f d /restore_test/out_dir");
+system("semanage fcontext -d -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Run restorecon with digests disabled, then check digests still do\n";
+print "not match as they were not updated\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -r $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Run restorecon with digests enabled, then check they match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Test SELINUX_RESTORECON_SKIP_DIGEST\n";
+system(
+    "runcon -t test_restorecon_t $basedir/restorecon_xattr -rD $v /restore_test"
+);
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+if ( $result >> 8 eq 4 ) {
+    print
+"Run selinux_restorecon with digests enabled and SELINUX_RESTORECON_SKIP_DIGEST = TRUE\n";
+    system(
+"runcon -t test_restorecon_t $basedir/selinux_restorecon_skip -rS $v /restore_test"
+    );
+
+    $result = system(
+"runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test"
+    );
+    ok( $result >> 8 eq 4 );
+}
+else {
+    print "Failed SELINUX_RESTORECON_SKIP_DIGEST test\n";
+    ok(0);
+}
+
+system(
+    "semanage fcontext -d -t in_file_t -f f \"/restore_test/out_dir(/.*)?\"");
+system("semanage fcontext -d -t test_file_t -f d /restore_test");
+system("rm -rf /restore_test");
+
+print
+  "Run restorecon on /sys with digests enabled, then check digests are not\n";
+print "written as /sys is SYSFS_MAGIC.\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /sys/fs/selinux");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests $v /sys/fs/selinux");
+ok( $result >> 8 eq 4 );
+
+if ($test_tmpfs) {
+    print
+"Run restorecon on /tmp with digests enabled, then check digests are not\n";
+    print "written as /tmp is TMPFS_MAGIC\n";
+    system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /tmp");
+    $result =
+      system("runcon -t test_restorecon_t $basedir/get_all_digests $v /tmp");
+    ok( $result >> 8 eq 4 );
+}
+
+exit;