aboutsummaryrefslogtreecommitdiff
path: root/tools/regression/security
diff options
context:
space:
mode:
Diffstat (limited to 'tools/regression/security')
-rw-r--r--tools/regression/security/access/Makefile7
-rw-r--r--tools/regression/security/access/testaccess.c358
-rw-r--r--tools/regression/security/cap_test/Makefile24
-rw-r--r--tools/regression/security/cap_test/cap_test.c118
-rw-r--r--tools/regression/security/cap_test/cap_test.h156
-rw-r--r--tools/regression/security/cap_test/cap_test.t10
-rw-r--r--tools/regression/security/cap_test/cap_test_capabilities.c558
-rw-r--r--tools/regression/security/cap_test/cap_test_capmode.c195
-rw-r--r--tools/regression/security/cap_test/cap_test_fcntl.c114
-rw-r--r--tools/regression/security/cap_test/cap_test_pdfork.c108
-rw-r--r--tools/regression/security/cap_test/cap_test_pdkill.c96
-rw-r--r--tools/regression/security/cap_test/cap_test_relative.c147
-rw-r--r--tools/regression/security/cap_test/cap_test_sysctl.c62
-rw-r--r--tools/regression/security/open_to_operation/Makefile5
-rw-r--r--tools/regression/security/open_to_operation/open_to_operation.c1250
-rw-r--r--tools/regression/security/proc_to_proc/Makefile10
-rw-r--r--tools/regression/security/proc_to_proc/README53
-rw-r--r--tools/regression/security/proc_to_proc/scenario.c431
-rw-r--r--tools/regression/security/proc_to_proc/scenario.h32
-rw-r--r--tools/regression/security/proc_to_proc/testuid.c63
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);
+}
+