diff options
Diffstat (limited to 'tools/regression/security/cap_test')
| -rw-r--r-- | tools/regression/security/cap_test/Makefile | 24 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test.c | 118 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test.h | 156 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test.t | 10 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_capabilities.c | 558 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_capmode.c | 195 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_fcntl.c | 114 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_pdfork.c | 108 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_pdkill.c | 96 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_relative.c | 147 | ||||
| -rw-r--r-- | tools/regression/security/cap_test/cap_test_sysctl.c | 62 |
11 files changed, 1588 insertions, 0 deletions
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); +} |
