diff options
Diffstat (limited to 'tools/regression/security')
20 files changed, 3797 insertions, 0 deletions
| diff --git a/tools/regression/security/access/Makefile b/tools/regression/security/access/Makefile new file mode 100644 index 000000000000..59f52230e0e5 --- /dev/null +++ b/tools/regression/security/access/Makefile @@ -0,0 +1,7 @@ +PROG=	testaccess +MAN= + +SRCS=	testaccess.c +CFLAGS	+= -Wall + +.include <bsd.prog.mk> diff --git a/tools/regression/security/access/testaccess.c b/tools/regression/security/access/testaccess.c new file mode 100644 index 000000000000..61e1b62f28b7 --- /dev/null +++ b/tools/regression/security/access/testaccess.c @@ -0,0 +1,358 @@ +/*- + * Copyright (c) 2001 Networks Associates Technology, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Written at NAI Labs at Network Associates by Robert Watson for the + * TrustedBSD Project. + * + * Work sponsored by Defense Advanced Research Projects Agency under the + * CHATS research program, CBOSS project. + */ + +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +/* + * Regression test to check some basic cases and see if access() and + * eaccess() are using the correct portions of the process credential. + * This test relies on running with privilege, and on UFS filesystem + * semantics.  Running the test in other environments may result + * in incorrect failure identification. + * + * Note that this may also break if filesystem access control is + * broken, or if the ability to check and set credentials is broken. + * + * Note that this test uses two hard-coded non-root UIDs; on multi-user + * systems, these UIDs may be in use by an untrusted user, in which + * case those users could interfere with the test. + */ + +#define	ROOT_UID	(uid_t)0 +#define	WHEEL_GID	(gid_t)0 +#define	TEST_UID_ONE	(uid_t)500 +#define	TEST_GID_ONE	(gid_t)500 +#define	TEST_UID_TWO	(uid_t)501 +#define	TEST_GID_TWO	(gid_t)501 + +struct file_description { +	char	*fd_name; +	uid_t	 fd_owner; +	gid_t	 fd_group; +	mode_t	 fd_mode; +}; + +static struct file_description fd_list[] = { +{"test1", ROOT_UID, WHEEL_GID, 0400}, +{"test2", TEST_UID_ONE, WHEEL_GID,0400}, +{"test3", TEST_UID_TWO, WHEEL_GID, 0400}, +{"test4", ROOT_UID, WHEEL_GID, 0040}, +{"test5", ROOT_UID, TEST_GID_ONE, 0040}, +{"test6", ROOT_UID, TEST_GID_TWO, 0040}}; + +static int fd_list_count = sizeof(fd_list) / +    sizeof(struct file_description); + +int +setup(void) +{ +	int i, error; + +	for (i = 0; i < fd_list_count; i++) { +		error = open(fd_list[i].fd_name, O_CREAT | O_EXCL, fd_list[i].fd_mode); +		if (error == -1) { +			perror("open"); +			return (error); +		} +		close(error); +		error = chown(fd_list[i].fd_name, fd_list[i].fd_owner, +		    fd_list[i].fd_group); +		if (error) { +			perror("chown"); +			return (error); +		} +	} +	return (0); +} + +int +restoreprivilege(void) +{ +	int error; + +	error = setreuid(ROOT_UID, ROOT_UID); +	if (error) +		return (error); + +	error = setregid(WHEEL_GID, WHEEL_GID); +	if (error) +		return (error); + +	return (0); +} + +int +reportprivilege(char *message) +{ +	uid_t euid, ruid, suid; +	gid_t egid, rgid, sgid; +	int error; + +	error = getresuid(&ruid, &euid, &suid); +	if (error) { +		perror("getresuid"); +		return (error); +	} + +	error = getresgid(&rgid, &egid, &sgid); +	if (error) { +		perror("getresgid"); +		return (error); +	} + +	if (message) +		printf("%s: ", message); +	printf("ruid: %d, euid: %d, suid: %d,     ", ruid, euid, suid); +	printf("rgid: %d, egid: %d, sgid: %d\n", rgid, egid, sgid); + +	return (0); +} + +int +cleanup(void) +{ +	int i, error; + +	error = restoreprivilege(); +	if (error) { +		perror("restoreprivilege"); +		return (error); +	} + +	for (i = 0; i < fd_list_count; i++) { +		error = unlink(fd_list[i].fd_name); +		if (error) +			return (error); +	} + +	return (0); +} + +int +main(int argc, char *argv[]) +{ +	int error, errorseen; + +	if (geteuid() != 0) { +		fprintf(stderr, "testaccess must run as root.\n"); +		exit (EXIT_FAILURE); +	} + +	error = setup(); +	if (error) { +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	/* Make sure saved uid is set appropriately. */ +	error = setresuid(ROOT_UID, ROOT_UID, ROOT_UID); +	if (error) { +		perror("setresuid"); +		cleanup(); +	} + +	/* Clear out additional groups. */ +	error = setgroups(0, NULL); +	if (error) { +		perror("setgroups"); +		cleanup(); +	} + +	/* Make sure saved gid is set appropriately. */ +	error = setresgid(WHEEL_GID, WHEEL_GID, WHEEL_GID); +	if (error) { +		perror("setresgid"); +		cleanup(); +	} + +	/* +	 * UID-only tests. +	 */ + +	/* Check that saved uid is not used */ +	error = setresuid(TEST_UID_ONE, TEST_UID_ONE, ROOT_UID); +	if (error) { +		perror("setresuid.1"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	errorseen = 0; + +	error = access("test1", R_OK); +	if (!error) { +		fprintf(stderr, "saved uid used instead of real uid\n"); +		errorseen++; +	} + +#ifdef EACCESS_AVAILABLE +	error = eaccess("test1", R_OK); +	if (!error) { +		fprintf(stderr, "saved uid used instead of effective uid\n"); +		errorseen++; +	} +#endif + +	error = restoreprivilege(); +	if (error) { +		perror("restoreprivilege"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	error = setresuid(TEST_UID_ONE, TEST_UID_TWO, ROOT_UID); +	if (error) { +		perror("setresid.2"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	/* Check that the real uid is used, not the effective uid */ +	error = access("test2", R_OK); +	if (error) { +		fprintf(stderr, "Effective uid was used instead of real uid in access().\n"); +		errorseen++; +	} + +#ifdef EACCESS_AVAILABLE +	/* Check that the effective uid is used, not the real uid */ +	error = eaccess("test3", R_OK); +	if (error) { +		fprintf(stderr, "Real uid was used instead of effective uid in eaccess().\n"); +		errorseen++; +	} +#endif + +	/* Check that the real uid is used, not the effective uid */ +	error = access("test3", R_OK); +	if (!error) { +		fprintf(stderr, "Effective uid was used instead of real uid in access().\n"); +		errorseen++; +	} + +#ifdef EACCESS_AVAILABLE +	/* Check that the effective uid is used, not the real uid */ +	error = eaccess("test2", R_OK); +	if (!error) { +		fprintf(stderr, "Real uid was used instead of effective uid in eaccess().\n"); +		errorseen++; +	} +#endif + +	error = restoreprivilege(); +	if (error) { +		perror("restoreprivilege"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	error = setresgid(TEST_GID_ONE, TEST_GID_TWO, WHEEL_GID); +	if (error) { +		perror("setresgid.1"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	/* Set non-root effective uid to avoid excess privilege. */ +	error = setresuid(TEST_UID_ONE, TEST_UID_ONE, ROOT_UID); +	if (error) { +		perror("setresuid.3"); +		cleanup(); +		exit (EXIT_FAILURE); +	} + +	/* Check that the saved gid is not used */ +	error = access("test4", R_OK); +	if (!error) { +		fprintf(stderr, "saved gid used instead of real gid\n"); +	} + +#ifdef EACCESS_AVAILABLE +	error = eaccess("test4", R_OK); +	if (!error) { +		fprintf(stderr, "saved gid used instead of effective gid\n"); +		errorseen++; +	} +#endif + +	/* Check that the real gid is used, not the effective gid */ +	error = access("test5", R_OK); +	if (error) { +		fprintf(stderr, "Effective gid was used instead of real gid in access().\n"); +		errorseen++; +	} + +#ifdef EACCESS_AVAILABLE +	/* Check that the effective gid is used, not the real gid */ +	error = eaccess("test6", R_OK); +	if (error) { +		fprintf(stderr, "Real gid was used instead of effective gid in eaccess().\n"); +		errorseen++; +	} +#endif + +	/* Check that the real gid is used, not the effective gid */ +	error = access("test6", R_OK); +	if (!error) { +		fprintf(stderr, "Effective gid was used instead of real gid in access().\n"); +		errorseen++; +	} + +#ifdef EACCESS_AVAILABLE +	/* Check that the effective gid is used, not the real gid */ +	error = eaccess("test5", R_OK); +	if (!error) { +		fprintf(stderr, "Real gid was used instead of effective gid in eaccess().\n"); +		errorseen++; +	} +#endif + +	fprintf(stderr, "%d errors seen.\n", errorseen); + +	/* +	 * All tests done, restore and clean up +	 */ + +	error = cleanup(); +	if (error) { +		perror("cleanup"); +		exit (EXIT_FAILURE); +	} + +	exit (EXIT_SUCCESS); +} diff --git a/tools/regression/security/cap_test/Makefile b/tools/regression/security/cap_test/Makefile new file mode 100644 index 000000000000..0fdc274edbed --- /dev/null +++ b/tools/regression/security/cap_test/Makefile @@ -0,0 +1,24 @@ +PROG=	cap_test +SRCS=	cap_test.c \ +	cap_test_capmode.c \ +	cap_test_capabilities.c \ +	cap_test_fcntl.c \ +	cap_test_pdfork.c \ +	cap_test_pdkill.c \ +	cap_test_relative.c \ +	cap_test_sysctl.c \ + +WARNS=	3 +MAN= + +# Use headers and libc from the build, if available. +KERNCONF?=	GENERIC +OBJROOT=	${.OBJDIR}/../../../../ +OBJKERN=	${OBJROOT}/sys/${KERNCONF} + +SRCROOT=	${.CURDIR}/../../../../ + +CFLAGS+=	-DMACHINE=\"${MACHINE}\" -I${OBJKERN} -I${SRCROOT}/sys +LDFLAGS+=	-L${OBJROOT}/lib/libc -lc + +.include <bsd.prog.mk> diff --git a/tools/regression/security/cap_test/cap_test.c b/tools/regression/security/cap_test/cap_test.c new file mode 100644 index 000000000000..3544bf690214 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2008-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/wait.h> + +#include <err.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "cap_test.h" + +/* Initialize a named test. Requires test_NAME() function to be declared. */ +#define	TEST_INIT(name)	{ #name, test_##name, FAILED } + +/* All of the tests that can be run. */ +struct test all_tests[] = { +	TEST_INIT(capmode), +	TEST_INIT(capabilities), +	TEST_INIT(fcntl), +	TEST_INIT(pdfork), +	TEST_INIT(pdkill), +	TEST_INIT(relative), +	TEST_INIT(sysctl), +}; +int test_count = sizeof(all_tests) / sizeof(struct test); + +int +main(int argc, char *argv[]) +{ + +	/* +	 * If no tests have been specified at the command line, run them all. +	 */ +	if (argc == 1) { +		printf("1..%d\n", test_count); + +		for (int i = 0; i < test_count; i++) +			execute(i + 1, all_tests + i); +		return (0); +	} + +	/* +	 * Otherwise, run only the specified tests. +	 */ +	printf("1..%d\n", argc - 1); +	for (int i = 1; i < argc; i++) +	{ +		int found = 0; +		for (int j = 0; j < test_count; j++) { +			if (strncmp(argv[i], all_tests[j].t_name, +			    strlen(argv[i])) == 0) { +				found = 1; +				execute(i, all_tests + j); +				break; +			} +		} + +		if (found == 0) +			errx(-1, "No such test '%s'", argv[i]); +	} + +	return (0); +} + +int +execute(int id, struct test *t) { +	int result; + +	pid_t pid = fork(); +	if (pid < 0) +		err(-1, "fork"); +	if (pid) { +		/* Parent: wait for result from child. */ +		int status; +		while (waitpid(pid, &status, 0) != pid) {} +		if (WIFEXITED(status)) +			result = WEXITSTATUS(status); +		else +			result = FAILED; +	} else { +		/* Child process: run the test. */ +		exit(t->t_run()); +	} + +	printf("%s %d - %s\n", +		(result == PASSED) ? "ok" : "not ok", +		id, t->t_name); + +	return (result); +} diff --git a/tools/regression/security/cap_test/cap_test.h b/tools/regression/security/cap_test/cap_test.h new file mode 100644 index 000000000000..da06fab0ef76 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test.h @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2008-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CAP_TEST_H +#define	CAP_TEST_H + +#include <err.h> + +/* + * Define a file required by a test. The test can't complete without the file, + * so if we don't have it, just die. + */ +#define	REQUIRE(fd)	do {						\ +	if ((fd) < 0)							\ +		err(-1, "%s:%d: Missing required file '%s'",		\ +			__FILE__, __LINE__, #fd);			\ +} while (0) + +/* Whether a test passed or failed. */ +#define	PASSED	0 +#define	FAILED	1 + +/* A test has failed; print a message and clear the 'success' flag. */ +#define	FAIL(...)	do {						\ +	warn(__VA_ARGS__);						\ +	success = FAILED;						\ +} while (0) + +/* As above, but do not print the errno message. */ +#define	FAILX(...)	do {						\ +	warnx(__VA_ARGS__);						\ +	success = FAILED;						\ +} while (0) + +/* Like an assertion, but don't kill the test, just fail and keep going. */ +#define	CHECK(condition) do {						\ +	if (!(condition))						\ +		FAILX("%s:%d: Assertion '%s' failed",			\ +		    __func__, __LINE__, #condition);			\ +} while (0) + +/* Make sure that a system call's return value is >= 0. */ +#define	CHECK_SYSCALL_SUCCEEDS(syscall, ...) do {			\ +	if (syscall(__VA_ARGS__) < 0)					\ +		FAIL("%s() at line %d: %s failed",			\ +		    __func__, __LINE__, #syscall);			\ +} while (0) + +/* Make sure that a system call fails with the correct errno. */ +#define	CHECK_SYSCALL_FAILS(expected_errno, syscall, ...)	do {	\ +	if (syscall(__VA_ARGS__) < 0) {					\ +		if (errno != expected_errno)				\ +			FAIL("%s() at line %d: %s",			\ +			    __func__, __LINE__, #syscall);		\ +	} else {							\ +		FAILX("%s() at line %d: %s succeeded; it should've failed", \ +		    __func__, __LINE__, #syscall);			\ +	}								\ +} while (0) + +/* Make sure that a system call fails, but not with a particular errno. */ +#define	CHECK_SYSCALL_FAILS_BUT_NOT_WITH(bad_errno, syscall, ...)	do { \ +	if (syscall(__VA_ARGS__) < 0) {					\ +		if (errno == bad_errno)					\ +			FAIL("%s() at line %d: %s",			\ +			    __func__, __LINE__, #syscall);		\ +	} else {							\ +		FAILX("%s() at line %d: %s succeeded; it should've failed", \ +		    __func__, __LINE__, #syscall);			\ +	}								\ +} while (0) + +/* A system call should fail with ECAPMODE. */ +#define	CHECK_CAPMODE(...) \ +	CHECK_SYSCALL_FAILS(ECAPMODE, __VA_ARGS__) + +/* A system call should fail, but not with ECAPMODE. */ +#define	CHECK_NOT_CAPMODE(...) \ +	CHECK_SYSCALL_FAILS_BUT_NOT_WITH(ECAPMODE, __VA_ARGS__) + +/* A system call should fail with ENOTCAPABLE. */ +#define	CHECK_NOTCAPABLE(...) \ +	CHECK_SYSCALL_FAILS(ENOTCAPABLE, __VA_ARGS__) + +/* Ensure that 'rights' are a subset of 'max'. */ +#define	CHECK_RIGHTS(rights, max)	do {				\ +	if ((success == PASSED) && (rights != max))			\ +		FAILX("Rights of opened file (%jx) > maximum (%jx)",	\ +		    (cap_rights_t) rights, (cap_rights_t) max);		\ +} while (0) + +/* Create a capability from a file descriptor, make sure it succeeds. */ +#define	MAKE_CAPABILITY(to, from, rights)	do {			\ +	cap_rights_t _rights;						\ +	REQUIRE(to = cap_new(from, rights));				\ +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, to, &_rights);		\ +	if ((success == PASSED) && (_rights != (rights)))		\ +		FAILX("New capability's rights (%jx) != %jx",		\ +		    _rights, (cap_rights_t) (rights));			\ +} while (0) + +/* + * A top-level test should take no arguments and return an integer value, + * either PASSED or FAILED. + * + * Errors such as SIGSEGV will be caught and interpreted as FAILED. + */ +typedef int	(*test_function)(void); + +/* Information about a test. */ +struct test { +	char		*t_name; +	test_function	 t_run; +	int		 t_result; +}; + +/* + * Run a test in a child process so that cap_enter(2) doesn't mess up + * subsequent tests. + */ +int	execute(int id, struct test*); + +int	test_capmode(void); +int	test_capabilities(void); +int	test_fcntl(void); +int	test_pdfork(void); +int	test_pdkill(void); +int	test_pdwait(void); +int	test_relative(void); +int	test_sysctl(void); + +#endif /* CAP_TEST_H */ diff --git a/tools/regression/security/cap_test/cap_test.t b/tools/regression/security/cap_test/cap_test.t new file mode 100644 index 000000000000..a13e1b204fb4 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test.t @@ -0,0 +1,10 @@ +#!/bin/sh +# +# + +if test -z "${DIR}" ; then DIR=$( make -V .OBJDIR ); fi +if test -z "${DIR}" ; then DIR=$( dirname $0 ); fi + +make > /dev/null || exit 1 +$DIR/cap_test $* + diff --git a/tools/regression/security/cap_test/cap_test_capabilities.c b/tools/regression/security/cap_test/cap_test_capabilities.c new file mode 100644 index 000000000000..b22749360bb1 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_capabilities.c @@ -0,0 +1,558 @@ +/*- + * Copyright (c) 2009-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * Copyright (c) 2012 FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by Pawel Jakub Dawidek under + * sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test whether various operations on capabilities are properly masked for + * various object types. + */ + +#include <sys/param.h> +#include <sys/capsicum.h> +#include <sys/errno.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include <err.h> +#include <fcntl.h> +#include <poll.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "cap_test.h" + +#define	SYSCALL_FAIL(syscall, message) \ +	FAIL("%s:\t%s (rights 0x%jx)", #syscall, message, rights) + +/* + * Ensure that, if the capability had enough rights for the system call to + * pass, then it did. Otherwise, ensure that the errno is ENOTCAPABLE; + * capability restrictions should kick in before any other error logic. + */ +#define	CHECK_RESULT(syscall, rights_needed, succeeded)	do {		\ +	if ((rights & (rights_needed)) == (rights_needed)) {		\ +		if (succeeded) {					\ +			if (success == -1)				\ +				success = PASSED;			\ +		} else {						\ +			SYSCALL_FAIL(syscall, "failed");		\ +		}							\ +	} else {							\ +		if (succeeded) {					\ +			FAILX("%s:\tsucceeded when it shouldn't have"	\ +			    " (rights 0x%jx)", #syscall,		\ +			    (uintmax_t)rights);				\ +		} else if (errno != ENOTCAPABLE) {			\ +			SYSCALL_FAIL(syscall, "errno != ENOTCAPABLE");	\ +		}							\ +	}								\ +	errno = 0;							\ +} while (0) + +/* + * As above, but for the special mmap() case: unmap after successful mmap(). + */ +#define	CHECK_MMAP_RESULT(rights_needed)	do {			\ +	if ((rights & (rights_needed)) == (rights_needed)) {		\ +		if (p == MAP_FAILED)					\ +			SYSCALL_FAIL(mmap, "failed");			\ +		else {							\ +			(void)munmap(p, getpagesize());			\ +			if (success == -1)				\ +				success = PASSED;			\ +		}							\ +	} else {							\ +		if (p != MAP_FAILED) {					\ +			FAILX("%s:\tsucceeded when it shouldn't have"	\ +			    " (rights 0x%jx)", "mmap", rights);		\ +			(void)munmap(p, getpagesize());			\ +		} else if (errno != ENOTCAPABLE)			\ +			SYSCALL_FAIL(syscall, "errno != ENOTCAPABLE");	\ +	}								\ +	errno = 0;							\ +} while (0) + +/* + * Given a file descriptor, create a capability with specific rights and + * make sure only those rights work.  +*/ +static int +try_file_ops(int filefd, int dirfd, cap_rights_t rights) +{ +	struct stat sb; +	struct statfs sf; +	cap_rights_t erights; +	int fd_cap, fd_capcap, dfd_cap; +	ssize_t ssize, ssize2; +	off_t off; +	void *p; +	char ch; +	int ret, is_nfs; +	struct pollfd pollfd; +	int success = -1; + +	REQUIRE(fstatfs(filefd, &sf)); +	is_nfs = (strcmp("nfs", sf.f_fstypename) == 0); + +	REQUIRE(fd_cap = cap_new(filefd, rights)); +	CHECK(cap_getrights(fd_cap, &erights) == 0); +	CHECK(rights == erights); +	REQUIRE(fd_capcap = cap_new(fd_cap, rights)); +	CHECK(cap_getrights(fd_capcap, &erights) == 0); +	CHECK(rights == erights); +	CHECK(fd_capcap != fd_cap); +	REQUIRE(dfd_cap = cap_new(dirfd, rights)); +	CHECK(cap_getrights(dfd_cap, &erights) == 0); +	CHECK(rights == erights); + +	ssize = read(fd_cap, &ch, sizeof(ch)); +	CHECK_RESULT(read, CAP_READ, ssize >= 0); + +	ssize = write(fd_cap, &ch, sizeof(ch)); +	CHECK_RESULT(write, CAP_WRITE, ssize >= 0); + +	off = lseek(fd_cap, 0, SEEK_SET); +	CHECK_RESULT(lseek, CAP_SEEK, off >= 0); + +	ssize = pread(fd_cap, &ch, sizeof(ch), 0); +	ssize2 = pread(fd_cap, &ch, sizeof(ch), 0); +	CHECK_RESULT(pread, CAP_PREAD, ssize >= 0); +	CHECK(ssize == ssize2); + +	ssize = pwrite(fd_cap, &ch, sizeof(ch), 0); +	CHECK_RESULT(pwrite, CAP_PWRITE, ssize >= 0); + +	p = mmap(NULL, getpagesize(), PROT_NONE, MAP_SHARED, fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP); + +	p = mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_R); + +	p = mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_W); + +	p = mmap(NULL, getpagesize(), PROT_EXEC, MAP_SHARED, fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_X); + +	p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, +	    fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_RW); + +	p = mmap(NULL, getpagesize(), PROT_READ | PROT_EXEC, MAP_SHARED, +	    fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_RX); + +	p = mmap(NULL, getpagesize(), PROT_EXEC | PROT_WRITE, MAP_SHARED, +	    fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_WX); + +	p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, +	    MAP_SHARED, fd_cap, 0); +	CHECK_MMAP_RESULT(CAP_MMAP_RWX); + +	ret = openat(dfd_cap, "cap_create", O_CREAT | O_RDONLY, 0600); +	CHECK_RESULT(openat(O_CREATE | O_RDONLY), +	    CAP_CREATE | CAP_READ | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_create", 0) == 0); +	ret = openat(dfd_cap, "cap_create", O_CREAT | O_WRONLY | O_APPEND, +	    0600); +	CHECK_RESULT(openat(O_CREATE | O_WRONLY | O_APPEND), +	    CAP_CREATE | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_create", 0) == 0); +	ret = openat(dfd_cap, "cap_create", O_CREAT | O_RDWR | O_APPEND, 0600); +	CHECK_RESULT(openat(O_CREATE | O_RDWR | O_APPEND), +	    CAP_CREATE | CAP_READ | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_create", 0) == 0); + +	ret = fsync(fd_cap); +	CHECK_RESULT(fsync, CAP_FSYNC, ret == 0); + +	ret = openat(dirfd, "cap_fsync", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDONLY); +	CHECK_RESULT(openat(O_FSYNC | O_RDONLY), +	    CAP_FSYNC | CAP_READ | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_FSYNC | O_WRONLY | O_APPEND); +	CHECK_RESULT(openat(O_FSYNC | O_WRONLY | O_APPEND), +	    CAP_FSYNC | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDWR | O_APPEND); +	CHECK_RESULT(openat(O_FSYNC | O_RDWR | O_APPEND), +	    CAP_FSYNC | CAP_READ | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDONLY); +	CHECK_RESULT(openat(O_SYNC | O_RDONLY), +	    CAP_FSYNC | CAP_READ | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_SYNC | O_WRONLY | O_APPEND); +	CHECK_RESULT(openat(O_SYNC | O_WRONLY | O_APPEND), +	    CAP_FSYNC | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDWR | O_APPEND); +	CHECK_RESULT(openat(O_SYNC | O_RDWR | O_APPEND), +	    CAP_FSYNC | CAP_READ | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(unlinkat(dirfd, "cap_fsync", 0) == 0); + +	ret = ftruncate(fd_cap, 0); +	CHECK_RESULT(ftruncate, CAP_FTRUNCATE, ret == 0); + +	ret = openat(dirfd, "cap_ftruncate", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_RDONLY); +	CHECK_RESULT(openat(O_TRUNC | O_RDONLY), +	    CAP_FTRUNCATE | CAP_READ | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_WRONLY); +	CHECK_RESULT(openat(O_TRUNC | O_WRONLY), +	    CAP_FTRUNCATE | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_RDWR); +	CHECK_RESULT(openat(O_TRUNC | O_RDWR), +	    CAP_FTRUNCATE | CAP_READ | CAP_WRITE | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(unlinkat(dirfd, "cap_ftruncate", 0) == 0); + +	ret = openat(dfd_cap, "cap_create", O_CREAT | O_WRONLY, 0600); +	CHECK_RESULT(openat(O_CREATE | O_WRONLY), +	    CAP_CREATE | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_create", 0) == 0); +	ret = openat(dfd_cap, "cap_create", O_CREAT | O_RDWR, 0600); +	CHECK_RESULT(openat(O_CREATE | O_RDWR), +	    CAP_CREATE | CAP_READ | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, +	    ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_create", 0) == 0); + +	ret = openat(dirfd, "cap_fsync", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_FSYNC | O_WRONLY); +	CHECK_RESULT(openat(O_FSYNC | O_WRONLY), +	    CAP_FSYNC | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDWR); +	CHECK_RESULT(openat(O_FSYNC | O_RDWR), +	    CAP_FSYNC | CAP_READ | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_SYNC | O_WRONLY); +	CHECK_RESULT(openat(O_SYNC | O_WRONLY), +	    CAP_FSYNC | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	ret = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDWR); +	CHECK_RESULT(openat(O_SYNC | O_RDWR), +	    CAP_FSYNC | CAP_READ | CAP_WRITE | CAP_SEEK | CAP_LOOKUP, ret >= 0); +	CHECK(ret == -1 || close(ret) == 0); +	CHECK(unlinkat(dirfd, "cap_fsync", 0) == 0); + +	/* +	 * Note: this is not expected to work over NFS. +	 */ +	ret = fchflags(fd_cap, UF_NODUMP); +	CHECK_RESULT(fchflags, CAP_FCHFLAGS, +	    ret == 0 || (is_nfs && errno == EOPNOTSUPP)); + +	ret = openat(dirfd, "cap_chflagsat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = chflagsat(dfd_cap, "cap_chflagsat", UF_NODUMP, 0); +	CHECK_RESULT(chflagsat, CAP_CHFLAGSAT | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_chflagsat", 0) == 0); + +	ret = fchown(fd_cap, -1, -1); +	CHECK_RESULT(fchown, CAP_FCHOWN, ret == 0); + +	ret = openat(dirfd, "cap_fchownat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = fchownat(dfd_cap, "cap_fchownat", -1, -1, 0); +	CHECK_RESULT(fchownat, CAP_FCHOWN | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_fchownat", 0) == 0); + +	ret = fchmod(fd_cap, 0644); +	CHECK_RESULT(fchmod, CAP_FCHMOD, ret == 0); + +	ret = openat(dirfd, "cap_fchmodat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = fchmodat(dfd_cap, "cap_fchmodat", 0600, 0); +	CHECK_RESULT(fchmodat, CAP_FCHMOD | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_fchmodat", 0) == 0); + +	ret = fcntl(fd_cap, F_GETFL); +	CHECK_RESULT(fcntl(F_GETFL), CAP_FCNTL, ret >= 0); +	ret = fcntl(fd_cap, F_SETFL, ret); +	CHECK_RESULT(fcntl(F_SETFL), CAP_FCNTL, ret == 0); + +	/* XXX flock */ + +	ret = fstat(fd_cap, &sb); +	CHECK_RESULT(fstat, CAP_FSTAT, ret == 0); + +	ret = openat(dirfd, "cap_fstatat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = fstatat(dfd_cap, "cap_fstatat", &sb, 0); +	CHECK_RESULT(fstatat, CAP_FSTAT | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_fstatat", 0) == 0); + +	ret = fstatfs(fd_cap, &sf); +	CHECK_RESULT(fstatfs, CAP_FSTATFS, ret == 0); + +	ret = fpathconf(fd_cap, _PC_NAME_MAX); +	CHECK_RESULT(fpathconf, CAP_FPATHCONF, ret >= 0); + +	ret = futimes(fd_cap, NULL); +	CHECK_RESULT(futimes, CAP_FUTIMES, ret == 0); + +	ret = openat(dirfd, "cap_futimesat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = futimesat(dfd_cap, "cap_futimesat", NULL); +	CHECK_RESULT(futimesat, CAP_FUTIMES | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_futimesat", 0) == 0); + +	ret = openat(dirfd, "cap_linkat_src", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = linkat(dirfd, "cap_linkat_src", dfd_cap, "cap_linkat_dst", 0); +	CHECK_RESULT(linkat, CAP_LINKAT | CAP_LOOKUP, ret == 0); +	CHECK(unlinkat(dirfd, "cap_linkat_src", 0) == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_linkat_dst", 0) == 0); + +	ret = mkdirat(dfd_cap, "cap_mkdirat", 0700); +	CHECK_RESULT(mkdirat, CAP_MKDIRAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_mkdirat", AT_REMOVEDIR) == 0); + +	ret = mkfifoat(dfd_cap, "cap_mkfifoat", 0600); +	CHECK_RESULT(mkfifoat, CAP_MKFIFOAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_mkfifoat", 0) == 0); + +	ret = mknodat(dfd_cap, "cap_mknodat", S_IFCHR | 0600, 0); +	CHECK_RESULT(mknodat, CAP_MKNODAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_mknodat", 0) == 0); + +	/* TODO: renameat(2) */ + +	ret = symlinkat("test", dfd_cap, "cap_symlinkat"); +	CHECK_RESULT(symlinkat, CAP_SYMLINKAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == -1 || unlinkat(dirfd, "cap_symlinkat", 0) == 0); + +	ret = openat(dirfd, "cap_unlinkat", O_CREAT, 0600); +	CHECK(ret >= 0); +	CHECK(close(ret) == 0); +	ret = unlinkat(dfd_cap, "cap_unlinkat", 0); +	CHECK_RESULT(unlinkat, CAP_UNLINKAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == 0 || unlinkat(dirfd, "cap_unlinkat", 0) == 0); +	ret = mkdirat(dirfd, "cap_unlinkat", 0700); +	CHECK(ret == 0); +	ret = unlinkat(dfd_cap, "cap_unlinkat", AT_REMOVEDIR); +	CHECK_RESULT(unlinkat, CAP_UNLINKAT | CAP_LOOKUP, ret == 0); +	CHECK(ret == 0 || unlinkat(dirfd, "cap_unlinkat", AT_REMOVEDIR) == 0); + +	pollfd.fd = fd_cap; +	pollfd.events = POLLIN | POLLERR | POLLHUP; +	pollfd.revents = 0; + +	ret = poll(&pollfd, 1, 0); +	if (rights & CAP_EVENT) +		CHECK((pollfd.revents & POLLNVAL) == 0); +	else +		CHECK((pollfd.revents & POLLNVAL) != 0); + +	/* XXX: select, kqueue */ + +	close(fd_cap); +	close(fd_capcap); + +	if (success == -1) { +		fprintf(stderr, "No tests for rights 0x%jx.\n", +		    (uintmax_t)rights); +		success = FAILED; +	} +	return (success); +} + +#define TRY(rights) \ +do { \ +	if (success == PASSED) \ +		success = try_file_ops(filefd, dirfd, (rights)); \ +	else \ +		/* We've already failed, but try the test anyway. */ \ +		try_file_ops(filefd, dirfd, (rights)); \ +} while (0) + +#define	KEEP_ERRNO(...)	do {						\ +	int _saved_errno = errno;					\ +	__VA_ARGS__;							\ +	errno = _saved_errno;						\ +} while (0); + +int +test_capabilities(void) +{ +	int filefd, dirfd, tmpfd; +	int success = PASSED; +	char file[] = "/tmp/cap_test.XXXXXXXXXX"; +	char dir[] = "/tmp/cap_test.XXXXXXXXXX"; + +	filefd = mkstemp(file); +	if (filefd < 0) +		err(-1, "mkstemp"); +	if (mkdtemp(dir) == NULL) { +		KEEP_ERRNO(unlink(file)); +		err(-1, "mkdtemp"); +	} +	dirfd = open(dir, O_RDONLY | O_DIRECTORY); +	if (dirfd == -1) { +		KEEP_ERRNO(unlink(file)); +		KEEP_ERRNO(rmdir(dir)); +		err(-1, "open"); +	} +	tmpfd = open("/tmp", O_RDONLY | O_DIRECTORY); +	if (tmpfd == -1) { +		KEEP_ERRNO(unlink(file)); +		KEEP_ERRNO(rmdir(dir)); +		err(-1, "open"); +	} + +	if (cap_enter() == -1) { +		KEEP_ERRNO(unlink(file)); +		KEEP_ERRNO(rmdir(dir)); +		err(-1, "cap_enter"); +	} + +	TRY(CAP_READ); +	TRY(CAP_WRITE); +	TRY(CAP_SEEK); +	TRY(CAP_PREAD); +	TRY(CAP_PWRITE); +	TRY(CAP_READ | CAP_WRITE); +	TRY(CAP_PREAD | CAP_PWRITE); +	TRY(CAP_MMAP); +	TRY(CAP_MMAP_R); +	TRY(CAP_MMAP_W); +	TRY(CAP_MMAP_X); +	TRY(CAP_MMAP_RW); +	TRY(CAP_MMAP_RX); +	TRY(CAP_MMAP_WX); +	TRY(CAP_MMAP_RWX); +	TRY(CAP_CREATE | CAP_READ | CAP_LOOKUP); +	TRY(CAP_CREATE | CAP_WRITE | CAP_LOOKUP); +	TRY(CAP_CREATE | CAP_READ | CAP_WRITE | CAP_LOOKUP); +#ifdef TODO +	TRY(CAP_FEXECVE); +#endif +	TRY(CAP_FSYNC); +	TRY(CAP_FSYNC | CAP_READ | CAP_LOOKUP); +	TRY(CAP_FSYNC | CAP_WRITE | CAP_LOOKUP); +	TRY(CAP_FSYNC | CAP_READ | CAP_WRITE | CAP_LOOKUP); +	TRY(CAP_FTRUNCATE); +	TRY(CAP_FTRUNCATE | CAP_READ | CAP_LOOKUP); +	TRY(CAP_FTRUNCATE | CAP_WRITE | CAP_LOOKUP); +	TRY(CAP_FTRUNCATE | CAP_READ | CAP_WRITE | CAP_LOOKUP); +#ifdef TODO +	TRY(CAP_FCHDIR); +#endif +	TRY(CAP_FCHFLAGS); +	TRY(CAP_FCHOWN); +	TRY(CAP_FCHOWN | CAP_LOOKUP); +	TRY(CAP_FCHMOD | CAP_LOOKUP); +	TRY(CAP_FCNTL); +#ifdef TODO +	TRY(CAP_FLOCK); +#endif +	TRY(CAP_FPATHCONF); +#ifdef TODO +	TRY(CAP_FSCK); +#endif +	TRY(CAP_FSTAT | CAP_LOOKUP); +	TRY(CAP_FSTATFS); +	TRY(CAP_FUTIMES | CAP_LOOKUP); +	TRY(CAP_LINKAT | CAP_LOOKUP); +	TRY(CAP_MKDIRAT | CAP_LOOKUP); +	TRY(CAP_MKFIFOAT | CAP_LOOKUP); +	TRY(CAP_MKNODAT | CAP_LOOKUP); +	TRY(CAP_SYMLINKAT | CAP_LOOKUP); +	TRY(CAP_UNLINKAT | CAP_LOOKUP); +	/* Rename needs CAP_RENAMEAT on source directory and CAP_LINKAT on destination directory. */ +	TRY(CAP_RENAMEAT | CAP_UNLINKAT | CAP_LOOKUP); +#ifdef TODO +	TRY(CAP_LOOKUP); +	TRY(CAP_EXTATTR_DELETE); +	TRY(CAP_EXTATTR_GET); +	TRY(CAP_EXTATTR_LIST); +	TRY(CAP_EXTATTR_SET); +	TRY(CAP_ACL_CHECK); +	TRY(CAP_ACL_DELETE); +	TRY(CAP_ACL_GET); +	TRY(CAP_ACL_SET); +	TRY(CAP_ACCEPT); +	TRY(CAP_BIND); +	TRY(CAP_CONNECT); +	TRY(CAP_GETPEERNAME); +	TRY(CAP_GETSOCKNAME); +	TRY(CAP_GETSOCKOPT); +	TRY(CAP_LISTEN); +	TRY(CAP_PEELOFF); +	TRY(CAP_RECV); +	TRY(CAP_SEND); +	TRY(CAP_SETSOCKOPT); +	TRY(CAP_SHUTDOWN); +	TRY(CAP_MAC_GET); +	TRY(CAP_MAC_SET); +	TRY(CAP_SEM_GETVALUE); +	TRY(CAP_SEM_POST); +	TRY(CAP_SEM_WAIT); +	TRY(CAP_POST_EVENT); +	TRY(CAP_EVENT); +	TRY(CAP_IOCTL); +	TRY(CAP_TTYHOOK); +	TRY(CAP_PDGETPID); +	TRY(CAP_PDWAIT); +	TRY(CAP_PDKILL); +#endif + +	(void)unlinkat(tmpfd, file + strlen("/tmp/"), 0); +	(void)unlinkat(tmpfd, dir + strlen("/tmp/"), AT_REMOVEDIR); + +	return (success); +} diff --git a/tools/regression/security/cap_test/cap_test_capmode.c b/tools/regression/security/cap_test/cap_test_capmode.c new file mode 100644 index 000000000000..9d1eaf45ea85 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_capmode.c @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 2008-2009 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test routines to make sure a variety of system calls are or are not + * available in capability mode.  The goal is not to see if they work, just + * whether or not they return the expected ECAPMODE. + */ + +#include <sys/param.h> +#include <sys/capsicum.h> +#include <sys/errno.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <machine/sysarch.h> + +#include <err.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "cap_test.h" + +#define	CHECK_SYSCALL_VOID_NOT_ECAPMODE(syscall, ...)	do {		\ +	errno = 0;							\ +	(void)syscall(__VA_ARGS__);					\ +	if (errno == ECAPMODE)						\ +		FAIL("capmode: %s failed with ECAPMODE", #syscall);	\ +} while (0) + +int +test_capmode(void) +{ +	struct statfs statfs; +	struct stat sb; +	long sysarch_arg = 0; +	int fd_close, fd_dir, fd_file, fd_socket, fd2[2]; +	int success = PASSED; +	pid_t pid, wpid; +	char ch; + +	/* Open some files to play with. */ +	REQUIRE(fd_file = open("/tmp/cap_capmode", O_RDWR|O_CREAT, 0644)); +	REQUIRE(fd_close = open("/dev/null", O_RDWR)); +	REQUIRE(fd_dir = open("/tmp", O_RDONLY)); +	REQUIRE(fd_socket = socket(PF_INET, SOCK_DGRAM, 0)); + +	/* Enter capability mode. */ +	REQUIRE(cap_enter()); + +	/* +	 * System calls that are not permitted in capability mode. +	 */ +	CHECK_CAPMODE(access, "/tmp/cap_capmode_access", F_OK); +	CHECK_CAPMODE(acct, "/tmp/cap_capmode_acct"); +	CHECK_CAPMODE(bind, PF_INET, NULL, 0); +	CHECK_CAPMODE(chdir, "/tmp/cap_capmode_chdir"); +	CHECK_CAPMODE(chflags, "/tmp/cap_capmode_chflags", UF_NODUMP); +	CHECK_CAPMODE(chmod, "/tmp/cap_capmode_chmod", 0644); +	CHECK_CAPMODE(chown, "/tmp/cap_capmode_chown", -1, -1); +	CHECK_CAPMODE(chroot, "/tmp/cap_capmode_chroot"); +	CHECK_CAPMODE(connect, PF_INET, NULL, 0); +	CHECK_CAPMODE(creat, "/tmp/cap_capmode_creat", 0644); +	CHECK_CAPMODE(fchdir, fd_dir); +	CHECK_CAPMODE(getfsstat, &statfs, sizeof(statfs), MNT_NOWAIT); +	CHECK_CAPMODE(link, "/tmp/foo", "/tmp/bar"); +	CHECK_CAPMODE(lstat, "/tmp/cap_capmode_lstat", &sb); +	CHECK_CAPMODE(mknod, "/tmp/capmode_mknod", 06440, 0); +	CHECK_CAPMODE(mount, "procfs", "/not_mounted", 0, NULL); +	CHECK_CAPMODE(open, "/dev/null", O_RDWR); +	CHECK_CAPMODE(readlink, "/tmp/cap_capmode_readlink", NULL, 0); +	CHECK_CAPMODE(revoke, "/tmp/cap_capmode_revoke"); +	CHECK_CAPMODE(stat, "/tmp/cap_capmode_stat", &sb); +	CHECK_CAPMODE(symlink, +	    "/tmp/cap_capmode_symlink_from", +	    "/tmp/cap_capmode_symlink_to"); +	CHECK_CAPMODE(unlink, "/tmp/cap_capmode_unlink"); +	CHECK_CAPMODE(unmount, "/not_mounted", 0); + +	/* +	 * System calls that are permitted in capability mode. +	 */ +	CHECK_SYSCALL_SUCCEEDS(close, fd_close); +	CHECK_SYSCALL_SUCCEEDS(dup, fd_file); +	CHECK_SYSCALL_SUCCEEDS(fstat, fd_file, &sb); +	CHECK_SYSCALL_SUCCEEDS(lseek, fd_file, 0, SEEK_SET); +	CHECK_SYSCALL_SUCCEEDS(msync, &fd_file, 8192, MS_ASYNC); +	CHECK_SYSCALL_SUCCEEDS(profil, NULL, 0, 0, 0); +	CHECK_SYSCALL_SUCCEEDS(read, fd_file, &ch, sizeof(ch)); +	CHECK_SYSCALL_SUCCEEDS(recvfrom, fd_socket, NULL, 0, 0, NULL, NULL); +	CHECK_SYSCALL_SUCCEEDS(setuid, getuid()); +	CHECK_SYSCALL_SUCCEEDS(write, fd_file, &ch, sizeof(ch)); + +	/* +	 * These calls will fail for lack of e.g. a proper name to send to, +	 * but they are allowed in capability mode, so errno != ECAPMODE. +	 */ +	CHECK_NOT_CAPMODE(accept, fd_socket, NULL, NULL); +	CHECK_NOT_CAPMODE(getpeername, fd_socket, NULL, NULL); +	CHECK_NOT_CAPMODE(getsockname, fd_socket, NULL, NULL); +	CHECK_NOT_CAPMODE(fchflags, fd_file, UF_NODUMP); +	CHECK_NOT_CAPMODE(recvmsg, fd_socket, NULL, 0); +	CHECK_NOT_CAPMODE(sendmsg, fd_socket, NULL, 0); +	CHECK_NOT_CAPMODE(sendto, fd_socket, NULL, 0, 0, NULL, 0); + +	/* +	 * System calls which should be allowed in capability mode, but which +	 * don't return errors, and are thus difficult to check. +	 * +	 * We will try anyway, by checking errno. +	 */ +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(getegid); +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(geteuid); +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(getgid); +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(getpid); +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(getppid); +	CHECK_SYSCALL_VOID_NOT_ECAPMODE(getuid); + +	/* +	 * Finally, tests for system calls that don't fit the pattern very well. +	 */ +	pid = fork(); +	if (pid >= 0) { +		if (pid == 0) { +			exit(0); +		} else if (pid > 0) { +			wpid = waitpid(pid, NULL, 0); +			if (wpid < 0) { +				if (errno != ECAPMODE) +					FAIL("capmode:waitpid"); +			} else +				FAIL("capmode:waitpid succeeded"); +		} +	} else +		FAIL("capmode:fork"); + +	if (getlogin() == NULL) +		FAIL("test_sycalls:getlogin %d", errno); + +	if (getsockname(fd_socket, NULL, NULL) < 0) { +		if (errno == ECAPMODE) +			FAIL("capmode:getsockname"); +	} + +	/* XXXRW: ktrace */ + +	if (pipe(fd2) == 0) { +		close(fd2[0]); +		close(fd2[1]); +	} else if (errno == ECAPMODE) +		FAIL("capmode:pipe"); + +	/* XXXRW: ptrace. */ + +	/* sysarch() is, by definition, architecture-dependent */ +#if defined (__amd64__) || defined (__i386__) +	CHECK_CAPMODE(sysarch, I386_SET_IOPERM, &sysarch_arg); +#else +	/* XXXJA: write a test for arm */ +	FAIL("capmode:no sysarch() test for current architecture"); +#endif + +	/* XXXRW: No error return from sync(2) to test. */ + +	return (success); +} diff --git a/tools/regression/security/cap_test/cap_test_fcntl.c b/tools/regression/security/cap_test/cap_test_fcntl.c new file mode 100644 index 000000000000..5ef9c55a3e4d --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_fcntl.c @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2009-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test that fcntl works in capability mode. + */ + +#include <sys/types.h> +#include <sys/capsicum.h> +#include <sys/errno.h> +#include <sys/ipc.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/wait.h> + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "cap_test.h" + +/* A filename->descriptor mapping. */ +struct fd { +	char	*f_name; +	int	 f_fd; +}; + +/* + * Ensure that fcntl() works consistently for both regular file descriptors and + * capability-wrapped ones. + */ +int +test_fcntl(void) +{ +	int success = PASSED; +	cap_rights_t rights = CAP_READ | CAP_FCNTL; + +	/* +	 * Open some files of different types, and wrap them in capabilities. +	 */ +	struct fd files[] = { +		{ "file",         open("/etc/passwd", O_RDONLY) }, +		{ "socket",       socket(PF_LOCAL, SOCK_STREAM, 0) }, +		{ "SHM",          shm_open(SHM_ANON, O_RDWR, 0600) }, +	}; +	REQUIRE(files[0].f_fd); +	REQUIRE(files[1].f_fd); +	REQUIRE(files[2].f_fd); + +	struct fd caps[] = { +		{ "file cap",     cap_new(files[0].f_fd, rights) }, +		{ "socket cap",   cap_new(files[1].f_fd, rights) }, +		{ "SHM cap",      cap_new(files[2].f_fd, rights) }, +	}; +	REQUIRE(caps[0].f_fd); +	REQUIRE(caps[1].f_fd); +	REQUIRE(caps[2].f_fd); + +	struct fd all[] = { +		files[0], caps[0], +		files[1], caps[1], +		files[2], caps[2], +	}; +	const size_t len = sizeof(all) / sizeof(struct fd); + +	REQUIRE(cap_enter()); + +	/* +	 * Ensure that we can fcntl() all the files that we opened above. +	 */ +	for (size_t i = 0; i < len; i++) +	{ +		struct fd f = all[i]; +		int cap; + +		CHECK_SYSCALL_SUCCEEDS(fcntl, f.f_fd, F_GETFL, 0); +		REQUIRE(cap = cap_new(f.f_fd, CAP_READ)); +		if (fcntl(f.f_fd, F_GETFL, 0) == -1) +			FAIL("Error calling fcntl('%s', F_GETFL)", f.f_name); +		else +			CHECK_NOTCAPABLE(fcntl, cap, F_GETFL, 0); +	} + +	return (success); +} + diff --git a/tools/regression/security/cap_test/cap_test_pdfork.c b/tools/regression/security/cap_test/cap_test_pdfork.c new file mode 100644 index 000000000000..fdd0b5d6ad04 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_pdfork.c @@ -0,0 +1,108 @@ +/*- + * Copyright (c) 2009-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test routines to make sure a variety of system calls are or are not + * available in capability mode.  The goal is not to see if they work, just + * whether or not they return the expected ECAPMODE. + */ + +#include <sys/types.h> + +#include <sys/capsium.h> +#include <sys/errno.h> +#include <sys/procdesc.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <err.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <stdio.h> +#include <time.h> + +#include "cap_test.h" + +int +test_pdfork(void) +{ +	struct stat stat; +	int success = PASSED; +	int pd, error; +	pid_t pid; +	time_t now; + +	//cap_enter(); + +	pid = pdfork(&pd, 0); +	if (pid < 0) +		err(-1, "pdfork"); + +	else if (pid == 0) { +		/* +		 * Child process. +		 * +		 * pd should not be a valid process descriptor. +		 */ +		error = pdgetpid(pd, &pid); +		if (error != -1) +			FAILX("pdgetpid succeeded"); +		else if (errno != EBADF) +			FAIL("pdgetpid failed, but errno != EBADF"); + +		exit(success); +	} + +	/* Parent process. Ensure that [acm]times have been set correctly. */ +	REQUIRE(fstat(pd, &stat)); + +	now = time(NULL); +	CHECK(now != (time_t)-1); + +	CHECK(now >= stat.st_birthtime); +	CHECK((now - stat.st_birthtime) < 2); +	CHECK(stat.st_birthtime == stat.st_atime); +	CHECK(stat.st_atime == stat.st_ctime); +	CHECK(stat.st_ctime == stat.st_mtime); + +	/* Wait for the child to finish. */ +	error = pdgetpid(pd, &pid); +	CHECK(error == 0); +	CHECK(pid > 0); + +	int status; +	while (waitpid(pid, &status, 0) != pid) {} +	if ((success == PASSED) && WIFEXITED(status)) +		success = WEXITSTATUS(status); +	else +		success = FAILED; + +	return (success); +} diff --git a/tools/regression/security/cap_test/cap_test_pdkill.c b/tools/regression/security/cap_test/cap_test_pdkill.c new file mode 100644 index 000000000000..2f2a18b3baa7 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_pdkill.c @@ -0,0 +1,96 @@ +/*- + * Copyright (c) 2009-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test routines to make sure a variety of system calls are or are not + * available in capability mode.  The goal is not to see if they work, just + * whether or not they return the expected ECAPMODE. + */ + +#include <sys/types.h> + +#include <sys/capsicum.h> +#include <sys/errno.h> +#include <sys/procdesc.h> +#include <sys/resource.h> +#include <sys/wait.h> + +#include <err.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <stdio.h> + +#include "cap_test.h" + +void handle_signal(int); +void handle_signal(int sig) { +	exit(PASSED); +} + +int +test_pdkill(void) +{ +	int success = PASSED; +	int pd, error; +	pid_t pid; + +	//cap_enter(); + +	error = pdfork(&pd, 0); +	if (error < 0) +		err(-1, "pdfork"); + +	else if (error == 0) { +		signal(SIGINT, handle_signal); +		sleep(3600); +		exit(FAILED); +	} + +	/* Parent process; find the child's PID (we'll need it later). */ +	error = pdgetpid(pd, &pid); +	if (error != 0) +		FAIL("pdgetpid"); + +	/* Kill the child! */ +	usleep(100); +	error = pdkill(pd, SIGINT); +	if (error != 0) +		FAIL("pdkill"); + +	/* Make sure the child finished properly. */ +	int status; +	while (waitpid(pid, &status, 0) != pid) {} +	if ((success == PASSED) && WIFEXITED(status)) +		success = WEXITSTATUS(status); +	else +		success = FAILED; + +	return (success); +} diff --git a/tools/regression/security/cap_test/cap_test_relative.c b/tools/regression/security/cap_test/cap_test_relative.c new file mode 100644 index 000000000000..ef2e561b0158 --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_relative.c @@ -0,0 +1,147 @@ +/*- + * Copyright (c) 2009-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/capsicum.h> +#include <sys/errno.h> + +#include <err.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "cap_test.h" + +/* + * Test openat(2) in a variety of sitations to ensure that it obeys Capsicum + * "strict relative" rules: + * + * 1. Use strict relative lookups in capability mode or when operating + *    relative to a capability. + * 2. When performing strict relative lookups, absolute paths (including + *    symlinks to absolute paths) are not allowed, nor are paths containing + *    '..' components. + */ +int +test_relative(void) +{ +	int success = PASSED; +	int fd, etc, etc_cap, etc_cap_ro, etc_cap_base, etc_cap_all; +	cap_rights_t baserights = CAP_READ | CAP_WRITE | CAP_SEEK | CAP_LOOKUP; +	cap_rights_t rights; + +	REQUIRE(etc = open("/etc/", O_RDONLY)); +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, etc, &rights); +	CHECK_RIGHTS(rights, CAP_ALL); + +	MAKE_CAPABILITY(etc_cap, etc, CAP_READ); +	MAKE_CAPABILITY(etc_cap_ro, etc, CAP_READ | CAP_LOOKUP); +	MAKE_CAPABILITY(etc_cap_base, etc, baserights); +	MAKE_CAPABILITY(etc_cap_all, etc, CAP_MASK_VALID); + +	/* +	 * openat(2) with regular file descriptors in non-capability mode +	 * should Just Work (tm). +	 */ +	CHECK_SYSCALL_SUCCEEDS(openat, etc, "/etc/passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, AT_FDCWD, "/etc/passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc, "../etc/passwd", O_RDONLY); + +	/* +	 * Lookups relative to capabilities should be strictly relative. +	 * +	 * When not in capability mode, we don't actually require CAP_LOOKUP. +	 */ +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_ro, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_base, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_all, "passwd", O_RDONLY); + +	CHECK_NOTCAPABLE(openat, etc_cap_ro, "../etc/passwd", O_RDONLY); +	CHECK_NOTCAPABLE(openat, etc_cap_base, "../etc/passwd", O_RDONLY); + +	/* +	 * This requires discussion: do we treat a capability with +	 * CAP_MASK_VALID *exactly* like a non-capability file descriptor +	 * (currently, the implementation says yes)? +	 */ +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_all, "../etc/passwd", O_RDONLY); + +	/* +	 * A file opened relative to a capability should itself be a capability. +	 */ +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, etc_cap_base, &rights); + +	REQUIRE(fd = openat(etc_cap_base, "passwd", O_RDONLY)); +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, fd, &rights); +	CHECK_RIGHTS(rights, baserights); + +	/* +	 * Enter capability mode; now ALL lookups are strictly relative. +	 */ +	REQUIRE(cap_enter()); + +	/* +	 * Relative lookups on regular files or capabilities with CAP_LOOKUP +	 * ought to succeed. +	 */ +	CHECK_SYSCALL_SUCCEEDS(openat, etc, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_ro, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_base, "passwd", O_RDONLY); +	CHECK_SYSCALL_SUCCEEDS(openat, etc_cap_all, "passwd", O_RDONLY); + +	/* +	 * Lookup relative to capabilities without CAP_LOOKUP should fail. +	 */ +	CHECK_NOTCAPABLE(openat, etc_cap, "passwd", O_RDONLY); + +	/* +	 * Absolute lookups should fail. +	 */ +	CHECK_CAPMODE(openat, AT_FDCWD, "/etc/passwd", O_RDONLY); +	CHECK_NOTCAPABLE(openat, etc, "/etc/passwd", O_RDONLY); + +	/* +	 * Lookups containing '..' should fail in capability mode. +	 */ +	CHECK_NOTCAPABLE(openat, etc, "../etc/passwd", O_RDONLY); +	CHECK_NOTCAPABLE(openat, etc_cap_ro, "../etc/passwd", O_RDONLY); +	CHECK_NOTCAPABLE(openat, etc_cap_base, "../etc/passwd", O_RDONLY); + +	REQUIRE(fd = openat(etc, "passwd", O_RDONLY)); +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, fd, &rights); + +	/* +	 * A file opened relative to a capability should itself be a capability. +	 */ +	REQUIRE(fd = openat(etc_cap_base, "passwd", O_RDONLY)); +	CHECK_SYSCALL_SUCCEEDS(cap_getrights, fd, &rights); +	CHECK_RIGHTS(rights, baserights); + +	return success; +} diff --git a/tools/regression/security/cap_test/cap_test_sysctl.c b/tools/regression/security/cap_test/cap_test_sysctl.c new file mode 100644 index 000000000000..dcc6970606da --- /dev/null +++ b/tools/regression/security/cap_test/cap_test_sysctl.c @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2008-2011 Robert N. M. Watson + * Copyright (c) 2011 Jonathan Anderson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test that various sysctls are (and aren't) available on capability mode. + */ + +#include <sys/types.h> +#include <sys/capsicum.h> +#include <sys/errno.h> +#include <sys/sysctl.h> +#include <sys/wait.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "cap_test.h" + +/* + * Certain sysctls are permitted in capability mode, but most are not.  Test + * for the ones that should be, and try one or two that shouldn't. + */ +int +test_sysctl(void) +{ +	int i, oid[2]; +	int success = PASSED; +	size_t len; + +	oid[0] = CTL_KERN; +	oid[1] = KERN_OSRELDATE; +	len = sizeof(i); +	CHECK(sysctl(oid, 2, &i, &len, NULL, 0) == 0); + +	return (success); +} diff --git a/tools/regression/security/open_to_operation/Makefile b/tools/regression/security/open_to_operation/Makefile new file mode 100644 index 000000000000..e319e4f78675 --- /dev/null +++ b/tools/regression/security/open_to_operation/Makefile @@ -0,0 +1,5 @@ +PROG=	open_to_operation +MAN= +WARNS?=	3 + +.include <bsd.prog.mk> diff --git a/tools/regression/security/open_to_operation/open_to_operation.c b/tools/regression/security/open_to_operation/open_to_operation.c new file mode 100644 index 000000000000..235b785b5993 --- /dev/null +++ b/tools/regression/security/open_to_operation/open_to_operation.c @@ -0,0 +1,1250 @@ +/*- + * Copyright (c) 2008 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * This regression test attempts to confirm that the flags used at open-time + * for a file descriptor properly limit system calls that should be affected + * by those flags.  Currently: + * + * System call                    Policy                      Tested + * __acl_aclcheck_fd(2)           any                         no + * __acl_delete_fd(2)             any                         no + * __acl_get_fd(2)                any                         no + * __acl_set_fd(2)                any                         no + * aio_fsync(2)                   any                         no + * aio_read(2)                    O_RDONLY or O_RDWR          yes + * aio_write(2)                   O_WRONLY or O_RDWR          yes + * dup(2)                         any                         yes + * dup2(2)                        any                         yes + * extattr_delete_fd(2)           O_WRONLY or O_RDWR          no + * extattr_get_fd(2)              O_RDONLY or O_RDWR          no + * extattr_list_fd(2)             O_RDONLY or O_RDWR          no + * extattr_set_fd(2)              O_WRONLY or O_RDWR          no + * fchdir(2)                      any directory               yes + * fchflags(2)                    any                         yes + * fchmod(2)                      any                         yes + * fchown(2)                      any                         yes + * flock(2)                       any                         yes + * fpathconf(2)                   any                         yes + * fstat(2)                       any                         yes + * fstatfs(2)                     any                         yes + * fsync(2)                       any                         yes + * ftruncate(2)                   O_WRONLY or O_RDWR          yes + * futimes(2)                     any                         yes + * getdents(2)                    O_RDONLY directory          yes + * lseek(2)                       any                         yes + * mmap(2) PROT_READ              O_RDONLY or O_RDWR          yes + * mmap(2) PROT_WRITE             O_WRONLY or O_RDWR          yes + * mmap(2) PROT_WRITE + MAP_PRIV  O_RDONLY or O_RDWR          yes + * mmap(2) PROT_EXEC              O_RDONLY or O_RDWR          yes + * pread(2)                       O_RDONLY or O_RDWR          yes + * preadv(2)                      O_RDONLY or O_RDWR          yes + * pwrite(2)                      O_WRONLY or O_RDWR          yes + * pwritev(2)                     O_WRONLY or O_RDWR          yes + * read(2)                        O_RDONLY or O_RDWR          yes + * readv(2)                       O_RDONLY or O_RDWR          yes + * sendfile(2)                    O_RDONLY or O_RDWR on file  yes + * write(2)                       O_WRONLY or O_RDWR          yes + * writev(2)                      O_WRONLY or O_RDWR          yes + *  + * These checks do not verify that original permissions would allow the + * operation or that open is properly impacted by permissions, just that once + * a file descriptor is held, open-time limitations are implemented. + * + * We do, however, test that directories cannot be opened as writable. + * + * XXXRW: Arguably we should also test combinations of bits to mmap(2). + * + * XXXRW: Should verify mprotect() remapping limits. + * + * XXXRW: kqueue(2)/kevent(2), poll(2), select(2) + * + * XXXRW: oaio_read(2), oaio_write(2), freebsd6_*(2). + * + * XXXRW: __mac*(2) + * + * XXXRW: message queue and shared memory fds? + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <aio.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define	PERM_FILE	0644		/* Allow read, write.  Someday exec? */ +#define	PERM_DIR	0755		/* Allow read, write, exec. */ + +/* + * Modes to try all tests with. + */ +static const int file_modes[] = { O_RDONLY, O_WRONLY, O_RDWR, +    O_RDONLY | O_TRUNC, O_WRONLY | O_TRUNC, O_RDWR | O_TRUNC }; +static const int file_modes_count = nitems(file_modes); + +static const int dir_modes[] = { O_RDONLY }; +static const int dir_modes_count = nitems(dir_modes); + +static int testnum; +static int aio_present; + +static void +ok_mode(const char *testname, const char *comment, int mode) +{ + +	testnum++; +	if (comment == NULL) +		printf("ok %d - %s # mode 0x%x\n", testnum, testname, mode); +	else +		printf("ok %d - %s # mode 0x%x - %s\n", testnum, testname, +		    mode, comment); +} + +static void +notok_mode(const char *testname, const char *comment, int mode) +{ + +	testnum++; +	if (comment == NULL) +		printf("not ok %d - %s # mode 0x%x\n", testnum, testname, +		    mode); +	else +		printf("not ok %d - %s # mode 0x%x - %s\n", testnum, testname, +		    mode, comment); +} + +/* + * Before we get started, confirm that we can't open directories writable. + */ +static void +try_directory_open(const char *testname, const char *directory, +    int mode, int expected_errno) +{ +	int dfd; + +	dfd = open(directory, mode); +	if (dfd >= 0) { +		if (expected_errno) +			notok_mode(testname, "opened", mode); +		else +			ok_mode(testname, NULL, mode); +		close(dfd); +	} else { +		if (expected_errno && expected_errno == errno) +			ok_mode(testname, NULL, mode); +		else if (expected_errno != 0) +			notok_mode(testname, "wrong errno", mode); +		else +			notok_mode(testname, "failed", mode); +	} +} + +static void +check_directory_open_modes(const char *directory, const int *modes, +    int modes_count) +{ +	int expected_errno, i, mode; + +	/* +	 * Directories should only open with O_RDONLY.  Notice that we use +	 * file_modes and not dirmodes. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		if (mode == O_RDONLY) +			expected_errno = 0; +		else +			expected_errno = EISDIR; +		try_directory_open(__func__, directory, mode, +		    expected_errno); +	} +} + +static void +check_dup(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int dfd, fd, i, mode; + +	/* +	 * dup() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		dfd = dup(fd); +		if (dfd >= 0) { +			ok_mode(testname, NULL, mode); +			close(dfd); +		} else +			notok_mode(testname, NULL, mode); +		close(fd); +	} +} + +static void +check_dup2(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int dfd, fd, i, mode; + +	/* +	 * dup2() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		dfd = dup2(fd, 500);	/* Arbitrary but high number. */ +		if (dfd >= 0) { +			ok_mode(testname, NULL, mode); +			close(dfd); +		} else +			notok_mode(testname, NULL, mode); +		close(fd); +	} +} + +static void +check_fchdir(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * fchdir() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fchdir(fd) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fchflags(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * fchflags() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fchflags(fd, UF_NODUMP) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fchmod(const char *testname, const char *path, int setmode, +    const int *modes, int modes_count) +{ +	int fd, i, mode; + +	/* +	 * fchmod() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fchmod(fd, setmode) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fchown(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * fchown() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fchown(fd, -1, -1) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_flock(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * flock() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (flock(fd, LOCK_EX) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fpathconf(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; +	long l; + +	/* +	 * fpathconf() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		l = fpathconf(fd, _PC_FILESIZEBITS); +		if (l >= 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fstat(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	struct stat sb; +	int fd, i, mode; + +	/* +	 * fstat() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fstat(fd, &sb) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fstatfs(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	struct statfs statfs; +	int fd, i, mode; + +	/* +	 * fstatfs() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fstatfs(fd, &statfs) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_fsync(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * fstatfs() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fsync(fd) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_ftruncate(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	struct stat sb; +	int fd, i, mode; + +	/* +	 * ftruncate() should work as long as long as (mode & O_ACCMODE) is +	 * O_RDWR or O_WRONLY. +	 * +	 * Directories should never be writable, so this test should always +	 * pass for directories... +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			notok_mode(testname, "truncate1 skipped", mode); +			notok_mode(testname, "truncate2 skipped", mode); +			notok_mode(testname, "truncate3 skipped", mode); +			continue; +		} +		if (fstat(fd, &sb) < 0) { +			notok_mode(testname, "fstat", mode); +			notok_mode(testname, "truncate1 skipped", mode); +			notok_mode(testname, "truncate2 skipped", mode); +			notok_mode(testname, "truncate3 skipped", mode); +			close(fd); +			continue; +		} +		ok_mode(testname, "setup", mode); + +		/* Truncate to grow file. */ +		if (ftruncate(fd, sb.st_size + 1) == 0) { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				ok_mode(testname, "truncate1 succeeded", +				    mode); +			else { +				notok_mode(testname, "truncate1 succeeded", +				    mode); +				notok_mode(testname, "truncate2 skipped", +				    mode); +				notok_mode(testname, "truncate3 skipped", +				    mode); +				close(fd); +				continue; +			} +		} else { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) { +				notok_mode(testname, "truncate1 failed", +				    mode); +				notok_mode(testname, "truncate2 skipped", +				    mode); +				notok_mode(testname, "truncate3 skipped", +				    mode); +				close(fd); +				continue; +			} else +				ok_mode(testname, "truncate1 failed", mode); +		} + +		/* Truncate to same size. */ +		if (ftruncate(fd, sb.st_size + 1) == 0) { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				ok_mode(testname, "truncate2 succeeded", +				    mode); +			else { +				notok_mode(testname, "truncate2 succeeded", +				    mode); +				notok_mode(testname, "truncate3 skipped", +				    mode); +				close(fd); +				continue; +			} +		} else { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) { +				notok_mode(testname, "truncate2 failed", +				    mode); +				notok_mode(testname, "truncate3 skipped", +				    mode); +				close(fd); +				continue; +			} else +				ok_mode(testname, "truncate2 failed", mode); +		} + +		/* Truncate to shrink. */ +		if (ftruncate(fd, sb.st_size) == 0) { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				ok_mode(testname, "truncate3 succeeded", +				    mode); +			else +				notok_mode(testname, "truncate3 succeeded", +				    mode); +		} else { +			if (((mode & O_ACCMODE) == O_WRONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				notok_mode(testname, "truncate3 failed", +				    mode); +			else +				ok_mode(testname, "truncate3 failed", mode); +		} +		close(fd); +	} +} + +static void +check_futimes(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * futimes() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (futimes(fd, NULL) == 0) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_lseek(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; + +	/* +	 * lseek() should work regardless of open mode. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (lseek(fd, 100, SEEK_SET) == 100) +			ok_mode(testname, NULL, mode); +		else +			notok_mode(testname, "failed", mode); +		close(fd); +	} +} + +static void +check_getdents(const char *testname, const char *path, int isdir, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char buf[8192]; + +	/* +	 * getdents() should always work on directories and never on files, +	 * assuming directories are always opened for read (which they are). +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (getdents(fd, buf, sizeof(buf)) >= 0) { +			if (isdir && ((mode & O_ACCMODE) == O_RDONLY)) +				ok_mode(testname, "directory succeeded", +				    mode); +			else if (isdir) +				notok_mode(testname, "directory succeeded", +				    mode); +			else +				notok_mode(testname, "file succeeded", mode); +		} else { +			if (isdir && ((mode & O_ACCMODE) == O_RDONLY)) +				notok_mode(testname, "directory failed", +				    mode); +			else if (isdir) +				ok_mode(testname, "directory failed", mode); +			else +				ok_mode(testname, "file failed", mode); +		} +		close(fd); +	} +} + +static void +check_sendfile(const char *testname, const char *path, int isdir, +    const int *modes, int modes_count) +{ +	int fd, i, mode, sv[2]; +	off_t sent; + +	/* +	 * sendfile() should work only on files, and only when the access mode +	 * is O_RDONLY or O_RDWR. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sv) < 0) { +			notok_mode(testname, "socketpair", mode); +			continue; +		} +		if (sendfile(fd, sv[0], 0, 1, NULL, &sent, 0) == 0) { +			if (isdir) +				notok_mode(testname, "directory succeeded", +				    mode); +			else if (((mode & O_ACCMODE) == O_RDONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				ok_mode(testname, "succeeded", mode); +			else +				notok_mode(testname, "succeeded", mode); +		} else { +			if (isdir) +				ok_mode(testname, "directory failed", mode); +			else if (((mode & O_ACCMODE) == O_RDONLY) || +			    ((mode & O_ACCMODE) == O_RDWR)) +				notok_mode(testname, "failed", mode); +			else +				ok_mode(testname, "failed", mode); +		} +		close(sv[0]); +		close(sv[1]); +		close(fd); +	} +} + +/* + * Various functions write, so just make write-like wrappers for them. + */ +typedef ssize_t (*write_fn)(int d, const void *buf, size_t nbytes); + +static ssize_t +writev_wrapper(int d, const void *buf, size_t nbytes) +{ +	struct iovec iov; + +	iov.iov_base = (void *)buf; +	iov.iov_len = nbytes; +	return (writev(d, &iov, 1)); +} + +static ssize_t +pwrite_wrapper(int d, const void *buf, size_t nbytes) +{ + +	return (pwrite(d, buf, nbytes, 0)); +} + +static ssize_t +pwritev_wrapper(int d, const void *buf, size_t nbytes) +{ +	struct iovec iov; + +	iov.iov_base = (void *)buf; +	iov.iov_len = nbytes; +	return (pwritev(d, &iov, 1, 0)); +} + +static ssize_t +aio_write_wrapper(int d, const void *buf, size_t nbytes) +{ +	struct aiocb aiocb; +	struct aiocb const *aiocb_array[] = { &aiocb }; + +	bzero(&aiocb, sizeof(aiocb)); +	aiocb.aio_fildes = d; +	aiocb.aio_buf = (void *)buf; +	aiocb.aio_nbytes = nbytes; +	if (aio_write(&aiocb) < 0) +		return (-1); +	aiocb_array[0] = &aiocb; +	if (aio_suspend(aiocb_array, 1, NULL) < 0) +		return (-1); +	return (aio_return(&aiocb)); +} + +static void +check_write(const char *testname, write_fn fn, const char *path, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char ch; + +	/* +	 * write() should never succeed for directories, but especially +	 * because they can only be opened read-only.  write() on files +	 * should succeed for O_WRONLY and O_RDWR descriptors. +	 */ + +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fn(fd, &ch, sizeof(ch)) < 0) { +			if ((mode & O_ACCMODE) == O_WRONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "write failed", mode); +			else +				ok_mode(testname, "write failed", mode); +		} else { +			if (!((mode & O_ACCMODE) == O_WRONLY || +			    (mode & O_ACCMODE) == O_RDWR)) +				notok_mode(testname, "write succeeded", mode); +			else +				ok_mode(testname, "write succeeded", mode); +		} +		close(fd); +	} +} + +/* + * Various functions read, so just make read-like wrappers for them. + */ +typedef ssize_t (*read_fn)(int d, void *buf, size_t nbytes); + +static ssize_t +readv_wrapper(int d, void *buf, size_t nbytes) +{ +	struct iovec iov; + +	iov.iov_base = buf; +	iov.iov_len = nbytes; +	return (readv(d, &iov, 1)); +} + +static ssize_t +pread_wrapper(int d, void *buf, size_t nbytes) +{ + +	return (pread(d, buf, nbytes, 0)); +} + +static ssize_t +preadv_wrapper(int d, void *buf, size_t nbytes) +{ +	struct iovec iov; + +	iov.iov_base = buf; +	iov.iov_len = nbytes; +	return (preadv(d, &iov, 1, 0)); +} + +static ssize_t +aio_read_wrapper(int d, void *buf, size_t nbytes) +{ +	struct aiocb aiocb; +	struct aiocb const *aiocb_array[] = { &aiocb }; + +	bzero(&aiocb, sizeof(aiocb)); +	aiocb.aio_fildes = d; +	aiocb.aio_buf = buf; +	aiocb.aio_nbytes = nbytes; +	if (aio_read(&aiocb) < 0) +		return (-1); +	if (aio_suspend(aiocb_array, 1, NULL) < 0) +		return (-1); +	return (aio_return(&aiocb)); +} + +static void +check_read(const char *testname, read_fn fn, const char *path, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char ch; + +	/* +	 * read() should (generally) succeeded on directories.  read() on +	 * files should succeed for O_RDONLY and O_RDWR descriptors. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		if (fn(fd, &ch, sizeof(ch)) < 0) { +			if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "read failed", mode); +			else +				ok_mode(testname, "read failed", mode); +		} else { +			if (!((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR)) +				notok_mode(testname, "read succeeded", mode); +			else +				ok_mode(testname, "read succeeded", mode); +		} +		close(fd); +	} +} + +static void +check_mmap_read(const char *testname, const char *path, int isdir, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char *addr; + +	/* +	 * mmap() read should fail for directories (ideally?) but succeed for +	 * O_RDONLY and O_RDWR file descriptors. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		addr = mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd, +		    0); +		if (addr == MAP_FAILED) { +			if (isdir) +				ok_mode(testname, "mmap dir failed", mode); +			else if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "mmap file failed", +				    mode); +			else +				ok_mode(testname, "mmap file failed", mode); +		} else { +			if (isdir) +				notok_mode(testname, "mmap dir succeeded", +				    mode); +			else if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				ok_mode(testname, "mmap file succeeded", +				    mode); +			else +				notok_mode(testname, "mmap file succeeded", +				    mode); +			(void)munmap(addr, getpagesize()); +		} +		close(fd); +	} +} + +static void +check_mmap_write(const char *testname, const char *path, const int *modes, +    int modes_count) +{ +	int fd, i, mode; +	char *addr; + +	/* +	 * mmap() will always fail for directories (ideally) as they are +	 * always open O_RDONLY.  Check for O_WRONLY or O_RDWR to permit a +	 * write mapping.  This variant does a MAP_SHARED mapping, but we +	 * are also interested in MAP_PRIVATE. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		addr = mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, fd, +		    0); +		if (addr == MAP_FAILED) { +			if ((mode & O_ACCMODE) == O_WRONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "mmap failed", +				    mode); +			else +				ok_mode(testname, "mmap failed", mode); +		} else { +			if ((mode & O_ACCMODE) == O_WRONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				ok_mode(testname, "mmap succeeded", +				    mode); +			else +				notok_mode(testname, "mmap succeeded", mode); +			(void)munmap(addr, getpagesize()); +		} +		close(fd); +	} +} + +static void +check_mmap_exec(const char *testname, const char *path, int isdir, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char *addr; + +	/* +	 * mmap() exec should fail for directories (ideally?) but succeed for +	 * O_RDONLY and O_RDWR file descriptors. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		addr = mmap(NULL, getpagesize(), PROT_EXEC, MAP_SHARED, fd, +		    0); +		if (addr == MAP_FAILED) { +			if (isdir) +				ok_mode(testname, "mmap dir failed", mode); +			else if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "mmap file failed", +				    mode); +			else +				ok_mode(testname, "mmap file failed", mode); +		} else { +			if (isdir) +				notok_mode(testname, "mmap dir succeeded", +				    mode); +			else +				ok_mode(testname, "mmap file succeeded", +				    mode); +			(void)munmap(addr, getpagesize()); +		} +		close(fd); +	} +} + +static void +check_mmap_write_private(const char *testname, const char *path, int isdir, +    const int *modes, int modes_count) +{ +	int fd, i, mode; +	char *addr; + +	/* +	 * mmap() write private should succeed for readable descriptors +	 * except for directories. +	 */ +	for (i = 0; i < modes_count; i++) { +		mode = modes[i]; +		fd = open(path, mode); +		if (fd < 0) { +			notok_mode(testname, "open", mode); +			continue; +		} +		addr = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, +		    MAP_PRIVATE, fd, 0); +		if (addr == MAP_FAILED) { +			if (isdir) +				ok_mode(testname, "mmap dir failed", mode); +			else if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				notok_mode(testname, "mmap file failed", +				    mode); +			else +				ok_mode(testname, "mmap file failed", mode); +		} else { +			if (isdir) +				notok_mode(testname, "mmap dir succeeded", +				    mode); +			else if ((mode & O_ACCMODE) == O_RDONLY || +			    (mode & O_ACCMODE) == O_RDWR) +				ok_mode(testname, "mmap file succeeded", +				    mode); +			else +				notok_mode(testname, "mmap file succeeded", +				    mode); +			(void)munmap(addr, getpagesize()); +		} +		close(fd); +	} +} + +int +main(void) +{ +	char dir_path[PATH_MAX], file_path[PATH_MAX]; +	int dummy, fd; +	size_t size; + +	aio_present = 0; +	size = sizeof(dummy); +	if (sysctlbyname("vfs.aio", &dummy, &size, NULL, 0) < 0) { +		if (errno == EISDIR) +			aio_present = 1; +	} + +	strlcpy(dir_path, "/tmp/open-dir.XXXXXXXXXXX", sizeof(dir_path)); +	if (mkdtemp(dir_path) == NULL) +		err(1, "mkdtemp"); +	if (chmod(dir_path, PERM_DIR) < 0) { +		warn("chmod %s", dir_path); +		(void)rmdir(dir_path); +		exit(1); +	} +	strlcpy(file_path, "/tmp/open-file.XXXXXXXXXXX", sizeof(file_path)); +	fd = mkstemp(file_path); +	if (fd < 0) { +		warn("mkstemp"); +		(void)rmdir(dir_path); +		exit(1); +	} +	close(fd); +	if (chmod(file_path, PERM_FILE) < 0) { +		warn("chmod %s", file_path); +		(void)unlink(file_path); +		(void)rmdir(dir_path); +		exit(1); +	} +	check_directory_open_modes(dir_path, file_modes, file_modes_count); + +	check_dup("check_dup_dir", dir_path, dir_modes, dir_modes_count); +	check_dup("check_dup_file", file_path, file_modes, file_modes_count); + +	check_dup2("check_dup2_dir", dir_path, dir_modes, dir_modes_count); +	check_dup2("check_dup2_file", file_path, file_modes, +	    file_modes_count); + +	check_fchdir("check_fchdir", dir_path, dir_modes, dir_modes_count); + +	check_fchflags("check_fchflags_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_fchflags("check_fchflags_file", file_path, file_modes, +	    file_modes_count); + +	check_fchmod("check_fchmod_dir", dir_path, PERM_DIR, dir_modes, +	    dir_modes_count); +	check_fchmod("check_fchmod_file", file_path, PERM_FILE, file_modes, +	    file_modes_count); + +	check_fchown("check_fchown_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_fchown("check_fchown_file", file_path, file_modes, +	    file_modes_count); + +	check_flock("check_flock_dir", dir_path, dir_modes, dir_modes_count); +	check_flock("check_flock_file", file_path, file_modes, +	    file_modes_count); + +	check_fpathconf("check_fpathconf_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_fpathconf("check_fpathconf_file", file_path, file_modes, +	    file_modes_count); + +	check_fstat("check_fstat_dir", dir_path, dir_modes, dir_modes_count); +	check_fstat("check_fstat_file", file_path, file_modes, +	    file_modes_count); + +	check_fstatfs("check_fstatfs_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_fstatfs("check_fstatfs_file", file_path, file_modes, +	    file_modes_count); + +	check_fsync("check_fsync_dir", dir_path, dir_modes, dir_modes_count); +	check_fsync("check_fsync_file", file_path, file_modes, +	    file_modes_count); + +	check_ftruncate("check_ftruncate_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_ftruncate("check_ftruncate_file", file_path, file_modes, +	    file_modes_count); + +	check_futimes("check_futimes_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_futimes("check_futimes_file", file_path, file_modes, +	    file_modes_count); + +	check_lseek("check_lseek_dir", dir_path, dir_modes, dir_modes_count); +	check_lseek("check_lseek_file", file_path, file_modes, +	    file_modes_count); + +	check_getdents("check_getdents_dir", dir_path, 1, dir_modes, +	    dir_modes_count); +	check_getdents("check_getdents_file", file_path, 0, file_modes, +	    file_modes_count); + +	check_sendfile("check_sendfile_dir", dir_path, 1, dir_modes, +	    dir_modes_count); +	check_sendfile("check_sendfile_file", file_path, 0, file_modes, +	    file_modes_count); + +	check_write("check_write_dir", write, dir_path, dir_modes, +	    dir_modes_count); +	check_write("check_write_file", write, file_path, file_modes, +	    file_modes_count); + +	check_write("check_writev_dir", writev_wrapper, dir_path, dir_modes, +	    dir_modes_count); +	check_write("check_writev_file", writev_wrapper, file_path, +	    file_modes, file_modes_count); + +	check_write("check_pwrite_dir", pwrite_wrapper, dir_path, dir_modes, +	    dir_modes_count); +	check_write("check_pwrite_file", pwrite_wrapper, file_path, +	    file_modes, file_modes_count); + +	check_write("check_pwritev_dir", pwritev_wrapper, dir_path, +	    dir_modes, dir_modes_count); +	check_write("check_pwritev_file", pwritev_wrapper, file_path, +	    file_modes, file_modes_count); + +	if (aio_present) { +		check_write("check_aio_write_dir", aio_write_wrapper, +		    dir_path, dir_modes, dir_modes_count); +		check_write("check_aio_write_file", aio_write_wrapper, +		    file_path, file_modes, file_modes_count); +	} + +	check_read("check_read_dir", read, dir_path, dir_modes, +	    dir_modes_count); +	check_read("check_read_file", read, file_path, file_modes, +	    file_modes_count); + +	check_read("check_readv_dir", readv_wrapper, dir_path, dir_modes, +	    dir_modes_count); +	check_read("check_readv_file", readv_wrapper, file_path, +	    file_modes, file_modes_count); + +	check_read("check_pread_dir", pread_wrapper, dir_path, dir_modes, +	    dir_modes_count); +	check_read("check_pread_file", pread_wrapper, file_path, +	    file_modes, file_modes_count); + +	check_read("check_preadv_dir", preadv_wrapper, dir_path, +	    dir_modes, dir_modes_count); +	check_read("check_preadv_file", preadv_wrapper, file_path, +	    file_modes, file_modes_count); + +	if (aio_present) { +		check_read("check_aio_read_dir", aio_read_wrapper, dir_path, +		    dir_modes, dir_modes_count); +		check_read("check_aio_read_file", aio_read_wrapper, +		    file_path, file_modes, file_modes_count); +	} + +	check_mmap_read("check_mmap_read_dir", dir_path, 1, dir_modes, +	    dir_modes_count); +	check_mmap_read("check_mmap_read_file", file_path, 0, file_modes, +	    file_modes_count); + +	check_mmap_write("check_mmap_write_dir", dir_path, dir_modes, +	    dir_modes_count); +	check_mmap_write("check_mmap_write_file", file_path, file_modes, +	    file_modes_count); + +	check_mmap_exec("check_mmap_exec_dir", dir_path, 1, dir_modes, +	    dir_modes_count); +	check_mmap_exec("check_mmap_exec_file", file_path, 0, file_modes, +	    file_modes_count); + +	check_mmap_write_private("check_mmap_write_private_dir", dir_path, 1, +	    dir_modes, dir_modes_count); +	check_mmap_write_private("check_mmap_write_private_file", file_path, +	    0, file_modes, file_modes_count); + +	(void)unlink(file_path); +	(void)rmdir(dir_path); +	exit(0); +} diff --git a/tools/regression/security/proc_to_proc/Makefile b/tools/regression/security/proc_to_proc/Makefile new file mode 100644 index 000000000000..984791c299b6 --- /dev/null +++ b/tools/regression/security/proc_to_proc/Makefile @@ -0,0 +1,10 @@ +PROG=	testuid +MAN= + +ADDITIONAL_FLAGS=-DSETSUGID_SUPPORTED +#ADDITIONAL_FLAGS=	-DSETSUGID_SUPPORTED_BUT_NO_LIBC_STUB +ADDITIONAL_FLAGS+=	-DCHECK_CRED_SET +CFLAGS+=	${ADDITIONAL_FLAGS} +SRCS=	testuid.c scenario.c + +.include <bsd.prog.mk> diff --git a/tools/regression/security/proc_to_proc/README b/tools/regression/security/proc_to_proc/README new file mode 100644 index 000000000000..9fe1e8cd35c8 --- /dev/null +++ b/tools/regression/security/proc_to_proc/README @@ -0,0 +1,53 @@ + +  Inter-Process Authorization Test Suite +  Robert Watson, TrustedBSD Project + +This test suite attempts to determine the behavior of inter-process +authorization policy present in the kernel.  It analyzes a series of +important scenarios using specifically crafted process credentials +and a set of operations.  It then reports on any divergence from the +expected results. + +Test operations: + +ptrace		cred1 attempts ptrace attach to cred2 +sighup		cred1 attempts SIGHUP of cred2 +sigsegv		cred1 attempts SIGSEGV of cred2 +see		cred1 attempts getpriority() on cred2 +sched		cred1 attempts setpriority() on cred2 + +Test scenarioes: + +priv on priv		root process on another root process +priv on unpriv1		root process on a non-root process +unpriv1 on priv		non-root process on a root process +unpriv1 on unpriv1	non-root process on a similar non-root process +unpriv1 on unpriv2	non-root process on a different non-root process +unpriv1 on daemon1	non-root process on a root daemon process acting with +			same non-root effective credentials +unpriv1 on daemon2	non-root process on a root daemon process acting with +			different non-root effective credentials +unpriv1 on setuid1	non-root process on a setuid-root process with same +			non-root real credentials +unpriv1 on setuid2	non-root process on a setuid-root process with +			different non-root real credentials + +The credential elements supported by the test suite are: + +	effective uid +	real uid +	saved uid +	P_SUGID flag + +Other untested aspects of interest include groups, as well as session +relationship.  Other test operations that might be of interest are SIGCONT, +and SIGIO. + +The current set of tests includes some tests where normally the P_SUGID +flag is set, but isn't in the test.  The result is that some tests fail +that may not reflect real-world software configurations.  However, they +do point to possible changes that could be made in the authorization system +to improve resilience to failure or violation of invariants. + +These tests rely on __setugid(), a system call enabled using options +REGRESSION. diff --git a/tools/regression/security/proc_to_proc/scenario.c b/tools/regression/security/proc_to_proc/scenario.c new file mode 100644 index 000000000000..5419058cce8f --- /dev/null +++ b/tools/regression/security/proc_to_proc/scenario.c @@ -0,0 +1,431 @@ +/*- + * Copyright (c) 2001 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/uio.h> +#include <sys/ptrace.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <sys/ktrace.h> + +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* + * Relevant parts of a process credential. + */ +struct cred { +	uid_t	cr_euid, cr_ruid, cr_svuid; +	int	cr_issetugid; +}; + +/* + * Description of a scenario. + */ +struct scenario { +	struct cred	*sc_cred1, *sc_cred2;	/* credentials of p1 and p2 */ +	int		sc_canptrace_errno;	/* desired ptrace failure */ +	int		sc_canktrace_errno;	/* desired ktrace failure */ +	int		sc_cansighup_errno;	/* desired SIGHUP failure */ +	int		sc_cansigsegv_errno;	/* desired SIGSEGV failure */ +	int		sc_cansee_errno;	/* desired getprio failure */ +	int		sc_cansched_errno;	/* desired setprio failure */ +	char		*sc_name;		/* test name */ +}; + +/* + * Table of relevant credential combinations. + */ +static struct cred creds[] = { +/*		euid	ruid	svuid	issetugid	*/ +/* 0 */ {	0,	0,	0,	0 },	/* privileged */ +/* 1 */ {	0,	0,	0,	1 },	/* privileged + issetugid */ +/* 2 */ {	1000,	1000,	1000,	0 },	/* unprivileged1 */ +/* 3 */ {	1000,	1000,	1000,	1 },	/* unprivileged1 + issetugid */ +/* 4 */ {	1001,	1001,	1001,	0 },	/* unprivileged2 */ +/* 5 */ {	1001,	1001,	1001,	1 },	/* unprivileged2 + issetugid */ +/* 6 */ {	1000,	0,	0,	0 },	/* daemon1 */ +/* 7 */ {	1000,	0,	0,	1 },	/* daemon1 + issetugid */ +/* 8 */ {	1001,	0,	0,	0 },	/* daemon2 */ +/* 9 */ {	1001,	0,	0,	1 },	/* daemon2 + issetugid */ +/* 10 */{	0,	1000,	1000,	0 },	/* setuid1 */ +/* 11 */{	0, 	1000,	1000,	1 },	/* setuid1 + issetugid */ +/* 12 */{	0,	1001,	1001,	0 },	/* setuid2 */ +/* 13 */{	0,	1001,	1001,	1 },	/* setuid2 + issetugid */ +}; + +/* + * Table of scenarios. + */ +static const struct scenario scenarios[] = { +/*	cred1		cred2		ptrace	ktrace, sighup	sigsegv	see	sched	name */ +/* privileged on privileged */ +{	&creds[0],	&creds[0],	0,	0,	0,	0,	0,	0,	"0. priv on priv"}, +{	&creds[0],	&creds[1],	0,	0,	0,	0,	0,	0,	"1. priv on priv"}, +{	&creds[1],	&creds[0],	0,	0,	0,	0,	0,	0,	"2. priv on priv"}, +{	&creds[1],	&creds[1],	0,	0,	0,	0,	0,	0,	"3. priv on priv"}, +/* privileged on unprivileged */ +{	&creds[0],	&creds[2],	0,	0,	0,	0,	0,	0,	"4. priv on unpriv1"}, +{	&creds[0],	&creds[3],	0,	0,	0,	0,	0,	0,	"5. priv on unpriv1"}, +{	&creds[1],	&creds[2],	0,	0,	0,	0,	0,	0,	"6. priv on unpriv1"}, +{	&creds[1],	&creds[3],	0,	0,	0,	0,	0,	0,	"7. priv on unpriv1"}, +/* unprivileged on privileged */ +{	&creds[2],	&creds[0],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"8. unpriv1 on priv"}, +{	&creds[2],	&creds[1],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"9. unpriv1 on priv"}, +{	&creds[3],	&creds[0],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"10. unpriv1 on priv"}, +{	&creds[3],	&creds[1],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"11. unpriv1 on priv"}, +/* unprivileged on same unprivileged */ +{	&creds[2],	&creds[2],	0,	0,	0,	0,	0,	0,	"12. unpriv1 on unpriv1"}, +{	&creds[2],	&creds[3],	EPERM,	EPERM,	0,	EPERM,	0,	0,	"13. unpriv1 on unpriv1"}, +{	&creds[3],	&creds[2],	0,	0,	0,	0,	0,	0,	"14. unpriv1 on unpriv1"}, +{	&creds[3],	&creds[3],	EPERM,	EPERM,	0,	EPERM,	0,	0,	"15. unpriv1 on unpriv1"}, +/* unprivileged on different unprivileged */ +{	&creds[2],	&creds[4],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"16. unpriv1 on unpriv2"}, +{	&creds[2],	&creds[5],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"17. unpriv1 on unpriv2"}, +{	&creds[3],	&creds[4],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"18. unpriv1 on unpriv2"}, +{	&creds[3],	&creds[5],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"19. unpriv1 on unpriv2"}, +/* unprivileged on daemon, same */ +{	&creds[2],	&creds[6],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"20. unpriv1 on daemon1"}, +{	&creds[2],	&creds[7],	EPERM,	EPERM,	EPERM,	EPERM,	0, 	EPERM,	"21. unpriv1 on daemon1"}, +{	&creds[3],	&creds[6],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"22. unpriv1 on daemon1"}, +{	&creds[3],	&creds[7],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"23. unpriv1 on daemon1"}, +/* unprivileged on daemon, different */ +{	&creds[2],	&creds[8],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"24. unpriv1 on daemon2"}, +{	&creds[2],	&creds[9],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"25. unpriv1 on daemon2"}, +{	&creds[3],	&creds[8],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"26. unpriv1 on daemon2"}, +{	&creds[3],	&creds[9],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"27. unpriv1 on daemon2"}, +/* unprivileged on setuid, same */ +{	&creds[2],	&creds[10],	EPERM,	EPERM,	0,	0,	0,	0,	"28. unpriv1 on setuid1"}, +{	&creds[2],	&creds[11],	EPERM,	EPERM,	0,	EPERM,	0,	0,	"29. unpriv1 on setuid1"}, +{	&creds[3],	&creds[10],	EPERM,	EPERM,	0,	0,	0,	0,	"30. unpriv1 on setuid1"}, +{	&creds[3],	&creds[11],	EPERM,	EPERM,	0,	EPERM,	0,	0,	"31. unpriv1 on setuid1"}, +/* unprivileged on setuid, different */ +{	&creds[2],	&creds[12],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"32. unpriv1 on setuid2"}, +{	&creds[2],	&creds[13],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"33. unpriv1 on setuid2"}, +{	&creds[3],	&creds[12],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"34. unpriv1 on setuid2"}, +{	&creds[3],	&creds[13],	EPERM,	EPERM,	EPERM,	EPERM,	0,	EPERM,	"35. unpriv1 on setuid2"}, +}; +int scenarios_count = sizeof(scenarios) / sizeof(struct scenario); + +/* + * Convert an error number to a compact string representation.  For now, + * implement only the error numbers we are likely to see. + */ +static char * +errno_to_string(int error) +{ + +	switch (error) { +	case EPERM: +		return ("EPERM"); +	case EACCES: +		return ("EACCES"); +	case EINVAL: +		return ("EINVAL"); +	case ENOSYS: +		return ("ENOSYS"); +	case ESRCH: +		return ("ESRCH"); +	case EOPNOTSUPP: +		return ("EOPNOTSUPP"); +	case 0: +		return ("0"); +	default: +		printf("%d\n", error); +		return ("unknown"); +	} +} + +/* + * Return a process credential describing the current process. + */ +static int +cred_get(struct cred *cred) +{ +	int error; + +	error = getresuid(&cred->cr_ruid, &cred->cr_euid, &cred->cr_svuid); +	if (error) +		return (error); + +	cred->cr_issetugid = issetugid(); + +	return (0); +} + +/* + * Userland stub for __setsugid() to take into account possible presence + * in C library, kernel, et al. + */ +int +setugid(int flag) +{ + +#ifdef SETSUGID_SUPPORTED +	return (__setugid(flag)); +#else +#ifdef SETSUGID_SUPPORTED_BUT_NO_LIBC_STUB +	return (syscall(374, flag)); +#else +	return (ENOSYS); +#endif +#endif +} + +/* + * Set the current process's credentials to match the passed credential. + */ +static int +cred_set(struct cred *cred) +{ +	int error; + +	error = setresuid(cred->cr_ruid, cred->cr_euid, cred->cr_svuid); +	if (error) +		return (error); + +	error = setugid(cred->cr_issetugid); +	if (error) { +		perror("__setugid"); +		return (error); +	} + +#ifdef CHECK_CRED_SET +	{ +		uid_t ruid, euid, svuid; +		error = getresuid(&ruid, &euid, &svuid); +		if (error) { +			perror("getresuid"); +			return (-1); +		} +		assert(ruid == cred->cr_ruid); +		assert(euid == cred->cr_euid); +		assert(svuid == cred->cr_svuid); +		assert(cred->cr_issetugid == issetugid()); +	} +#endif /* !CHECK_CRED_SET */ + +	return (0); +} + +/* + * Print the passed process credential to the passed I/O stream. + */ +static void +cred_print(FILE *output, struct cred *cred) +{ + +	fprintf(output, "(e:%d r:%d s:%d P_SUGID:%d)", cred->cr_euid, +	    cred->cr_ruid, cred->cr_svuid, cred->cr_issetugid); +} + +#define	LOOP_PTRACE	0 +#define	LOOP_KTRACE	1 +#define	LOOP_SIGHUP	2 +#define	LOOP_SIGSEGV	3 +#define	LOOP_SEE	4 +#define	LOOP_SCHED	5 +#define	LOOP_MAX	LOOP_SCHED + +/* + * Enact a scenario by looping through the four test cases for the scenario, + * spawning off pairs of processes with the desired credentials, and + * reporting results to stdout. + */ +static int +enact_scenario(int scenario) +{ +	pid_t pid1, pid2; +	char *name, *tracefile; +	int error, desirederror, loop; + +	for (loop = 0; loop < LOOP_MAX+1; loop++) { +		/* +		 * Spawn the first child, target of the operation. +		 */ +		pid1 = fork(); +		switch (pid1) { +		case -1: +			return (-1); +		case 0: +			/* child */ +			error = cred_set(scenarios[scenario].sc_cred2); +			if (error) { +				perror("cred_set"); +				return (error); +			} +			/* 200 seconds should be plenty of time. */ +			sleep(200); +			exit(0); +		default: +			/* parent */ +			break; +		} + +		/* +		 * XXX +		 * This really isn't ideal -- give proc 1 a chance to set +		 * its credentials, or we may get spurious errors.  Really, +		 * some for of IPC should be used to allow the parent to +		 * wait for the first child to be ready before spawning +		 * the second child. +		 */ +		sleep(1); + +		/* +		 * Spawn the second child, source of the operation. +		 */ +		pid2 = fork(); +		switch (pid2) { +		case -1: +			return (-1); +	 +		case 0: +			/* child */ +			error = cred_set(scenarios[scenario].sc_cred1); +			if (error) { +				perror("cred_set"); +				return (error); +			} +	 +			/* +			 * Initialize errno to zero so as to catch any +			 * generated errors.  In each case, perform the +			 * operation.  Preserve the error number for later +			 * use so it doesn't get stomped on by any I/O. +			 * Determine the desired error for the given case +			 * by extracting it from the scenario table. +			 * Initialize a function name string for output +			 * prettiness. +			 */ +			errno = 0; +			switch (loop) { +			case LOOP_PTRACE: +				error = ptrace(PT_ATTACH, pid1, NULL, 0); +				error = errno; +				name = "ptrace"; +				desirederror = +				    scenarios[scenario].sc_canptrace_errno; +				break; +			case LOOP_KTRACE: +				tracefile = mktemp("/tmp/testuid_ktrace.XXXXXX"); +				if (tracefile == NULL) { +					error = errno; +					perror("mktemp"); +					break; +				} +				error = ktrace(tracefile, KTROP_SET, +				    KTRFAC_SYSCALL, pid1); +				error = errno; +				name = "ktrace"; +				desirederror = +				    scenarios[scenario].sc_canktrace_errno; +				unlink(tracefile); +				break; +			case LOOP_SIGHUP: +				error = kill(pid1, SIGHUP); +				error = errno; +				name = "sighup"; +				desirederror = +				    scenarios[scenario].sc_cansighup_errno; +				break; +			case LOOP_SIGSEGV: +				error = kill(pid1, SIGSEGV); +				error = errno; +				name = "sigsegv"; +				desirederror = +				    scenarios[scenario].sc_cansigsegv_errno; +				break; +			case LOOP_SEE: +				getpriority(PRIO_PROCESS, pid1); +				error = errno; +				name = "see"; +				desirederror = +				    scenarios[scenario].sc_cansee_errno; +				break; +			case LOOP_SCHED: +				error = setpriority(PRIO_PROCESS, pid1, +				   0); +				error = errno; +				name = "sched"; +				desirederror = +				    scenarios[scenario].sc_cansched_errno; +				break; +			default: +				name = "broken"; +			} + +			if (error != desirederror) { +				fprintf(stdout, +				    "[%s].%s: expected %s, got %s\n  ", +				    scenarios[scenario].sc_name, name, +				    errno_to_string(desirederror), +				    errno_to_string(error)); +				cred_print(stdout, +				    scenarios[scenario].sc_cred1); +				cred_print(stdout, +				    scenarios[scenario].sc_cred2); +				fprintf(stdout, "\n"); +			} + +			exit(0); + +		default: +			/* parent */ +			break; +		} + +		error = waitpid(pid2, NULL, 0); +		/* +		 * Once pid2 has died, it's safe to kill pid1, if it's still +		 * alive.  Mask signal failure in case the test actually +		 * killed pid1 (not unlikely: can occur in both signal and +		 * ptrace cases). +		 */ +		kill(pid1, SIGKILL); +		error = waitpid(pid2, NULL, 0); +	} +	 +	return (0); +} + +void +enact_scenarios(void) +{ +	int i, error; + +	for (i = 0; i < scenarios_count; i++) { +		error = enact_scenario(i); +		if (error) +			perror("enact_scenario"); +	} +} diff --git a/tools/regression/security/proc_to_proc/scenario.h b/tools/regression/security/proc_to_proc/scenario.h new file mode 100644 index 000000000000..4bc8b33a9f71 --- /dev/null +++ b/tools/regression/security/proc_to_proc/scenario.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 2001 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _SCENARIO_H +#define _SCENARIO_H + +int	setugid __P((int flag)); +int	enact_scenarios __P((void)); + +#endif /* !_SCENARIO_H */ diff --git a/tools/regression/security/proc_to_proc/testuid.c b/tools/regression/security/proc_to_proc/testuid.c new file mode 100644 index 000000000000..0f499ede771c --- /dev/null +++ b/tools/regression/security/proc_to_proc/testuid.c @@ -0,0 +1,63 @@ +/*- + * Copyright (c) 2001 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> + +#include <stdio.h> +#include <unistd.h> + +#include "scenario.h" + +int +main(int argc, char *argv[]) +{ +	int error; + +	fprintf(stderr, "test capabilities: "); +#ifdef SETSUGID_SUPPORTED +	fprintf(stderr, "[SETSUGID_SUPPORTED] "); +#endif +#ifdef SETSUGID_SUPPORTED_BUT_NO_LIBC_STUB +	fprintf(stderr, "[SETSUGID_SUPPORTED_BUT_NO_LIBC_STUB] "); +#endif +#ifdef CHECK_CRED_SET +	fprintf(stderr, "[CHECK_CRED_SET] "); +#endif +	fprintf(stderr, "\n"); + +	error = setugid(1); +	if (error) { +		perror("setugid"); +		fprintf(stderr, +		    "This test suite requires options REGRESSION\n"); +		return (-1); +	} + +	enact_scenarios(); + +	return (0); +} + | 
