Index: projects/capsicum-test/contrib/capsicum-test/capability-fd.cc =================================================================== --- projects/capsicum-test/contrib/capsicum-test/capability-fd.cc (revision 345423) +++ projects/capsicum-test/contrib/capsicum-test/capability-fd.cc (revision 345424) @@ -1,1271 +1,1309 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" /* Utilities for printing rights information */ /* Written in C style to allow for: */ /* TODO(drysdale): migrate these to somewhere in libcaprights/ */ #define RIGHTS_INFO(RR) { (RR), #RR} typedef struct { uint64_t right; const char* name; } right_info; static right_info known_rights[] = { /* Rights that are common to all versions of Capsicum */ RIGHTS_INFO(CAP_READ), RIGHTS_INFO(CAP_WRITE), RIGHTS_INFO(CAP_SEEK_TELL), RIGHTS_INFO(CAP_SEEK), RIGHTS_INFO(CAP_PREAD), RIGHTS_INFO(CAP_PWRITE), RIGHTS_INFO(CAP_MMAP), RIGHTS_INFO(CAP_MMAP_R), RIGHTS_INFO(CAP_MMAP_W), RIGHTS_INFO(CAP_MMAP_X), RIGHTS_INFO(CAP_MMAP_RW), RIGHTS_INFO(CAP_MMAP_RX), RIGHTS_INFO(CAP_MMAP_WX), RIGHTS_INFO(CAP_MMAP_RWX), RIGHTS_INFO(CAP_CREATE), RIGHTS_INFO(CAP_FEXECVE), RIGHTS_INFO(CAP_FSYNC), RIGHTS_INFO(CAP_FTRUNCATE), RIGHTS_INFO(CAP_LOOKUP), RIGHTS_INFO(CAP_FCHDIR), RIGHTS_INFO(CAP_FCHFLAGS), RIGHTS_INFO(CAP_CHFLAGSAT), RIGHTS_INFO(CAP_FCHMOD), RIGHTS_INFO(CAP_FCHMODAT), RIGHTS_INFO(CAP_FCHOWN), RIGHTS_INFO(CAP_FCHOWNAT), RIGHTS_INFO(CAP_FCNTL), RIGHTS_INFO(CAP_FLOCK), RIGHTS_INFO(CAP_FPATHCONF), RIGHTS_INFO(CAP_FSCK), RIGHTS_INFO(CAP_FSTAT), RIGHTS_INFO(CAP_FSTATAT), RIGHTS_INFO(CAP_FSTATFS), RIGHTS_INFO(CAP_FUTIMES), RIGHTS_INFO(CAP_FUTIMESAT), RIGHTS_INFO(CAP_MKDIRAT), RIGHTS_INFO(CAP_MKFIFOAT), RIGHTS_INFO(CAP_MKNODAT), RIGHTS_INFO(CAP_RENAMEAT_SOURCE), RIGHTS_INFO(CAP_SYMLINKAT), RIGHTS_INFO(CAP_UNLINKAT), RIGHTS_INFO(CAP_ACCEPT), RIGHTS_INFO(CAP_BIND), RIGHTS_INFO(CAP_CONNECT), RIGHTS_INFO(CAP_GETPEERNAME), RIGHTS_INFO(CAP_GETSOCKNAME), RIGHTS_INFO(CAP_GETSOCKOPT), RIGHTS_INFO(CAP_LISTEN), RIGHTS_INFO(CAP_PEELOFF), RIGHTS_INFO(CAP_RECV), RIGHTS_INFO(CAP_SEND), RIGHTS_INFO(CAP_SETSOCKOPT), RIGHTS_INFO(CAP_SHUTDOWN), RIGHTS_INFO(CAP_BINDAT), RIGHTS_INFO(CAP_CONNECTAT), RIGHTS_INFO(CAP_LINKAT_SOURCE), RIGHTS_INFO(CAP_RENAMEAT_TARGET), RIGHTS_INFO(CAP_SOCK_CLIENT), RIGHTS_INFO(CAP_SOCK_SERVER), RIGHTS_INFO(CAP_MAC_GET), RIGHTS_INFO(CAP_MAC_SET), RIGHTS_INFO(CAP_SEM_GETVALUE), RIGHTS_INFO(CAP_SEM_POST), RIGHTS_INFO(CAP_SEM_WAIT), RIGHTS_INFO(CAP_EVENT), RIGHTS_INFO(CAP_KQUEUE_EVENT), RIGHTS_INFO(CAP_IOCTL), RIGHTS_INFO(CAP_TTYHOOK), RIGHTS_INFO(CAP_PDWAIT), RIGHTS_INFO(CAP_PDGETPID), RIGHTS_INFO(CAP_PDKILL), RIGHTS_INFO(CAP_EXTATTR_DELETE), RIGHTS_INFO(CAP_EXTATTR_GET), RIGHTS_INFO(CAP_EXTATTR_LIST), RIGHTS_INFO(CAP_EXTATTR_SET), RIGHTS_INFO(CAP_ACL_CHECK), RIGHTS_INFO(CAP_ACL_DELETE), RIGHTS_INFO(CAP_ACL_GET), RIGHTS_INFO(CAP_ACL_SET), RIGHTS_INFO(CAP_KQUEUE_CHANGE), RIGHTS_INFO(CAP_KQUEUE), /* Rights that are only present in some version or some OS, and so are #ifdef'ed */ /* LINKAT got split */ #ifdef CAP_LINKAT RIGHTS_INFO(CAP_LINKAT), #endif #ifdef CAP_LINKAT_SOURCE RIGHTS_INFO(CAP_LINKAT_SOURCE), #endif #ifdef CAP_LINKAT_TARGET RIGHTS_INFO(CAP_LINKAT_TARGET), #endif /* Linux aliased some FD operations for pdgetpid/pdkill */ #ifdef CAP_PDGETPID_FREEBSD RIGHTS_INFO(CAP_PDGETPID_FREEBSD), #endif #ifdef CAP_PDKILL_FREEBSD RIGHTS_INFO(CAP_PDKILL_FREEBSD), #endif /* Linux-specific rights */ #ifdef CAP_FSIGNAL RIGHTS_INFO(CAP_FSIGNAL), #endif #ifdef CAP_EPOLL_CTL RIGHTS_INFO(CAP_EPOLL_CTL), #endif #ifdef CAP_NOTIFY RIGHTS_INFO(CAP_NOTIFY), #endif #ifdef CAP_SETNS RIGHTS_INFO(CAP_SETNS), #endif #ifdef CAP_PERFMON RIGHTS_INFO(CAP_PERFMON), #endif #ifdef CAP_BPF RIGHTS_INFO(CAP_BPF), #endif /* Rights in later versions of FreeBSD (>10.0) */ }; void ShowCapRights(FILE *out, int fd) { size_t ii; bool first = true; cap_rights_t rights; CAP_SET_NONE(&rights); if (cap_rights_get(fd, &rights) < 0) { fprintf(out, "Failed to get rights for fd %d: errno %d\n", fd, errno); return; } /* First print out all known rights */ size_t num_known = (sizeof(known_rights)/sizeof(known_rights[0])); for (ii = 0; ii < num_known; ii++) { if (cap_rights_is_set(&rights, known_rights[ii].right)) { if (!first) fprintf(out, ","); first = false; fprintf(out, "%s", known_rights[ii].name); } } /* Now repeat the loop, clearing rights we know of; this needs to be * a separate loop because some named rights overlap. */ for (ii = 0; ii < num_known; ii++) { cap_rights_clear(&rights, known_rights[ii].right); } /* The following relies on the internal structure of cap_rights_t to * try to show rights we don't know about. */ for (ii = 0; ii < (size_t)CAPARSIZE(&rights); ii++) { uint64_t bits = (rights.cr_rights[0] & 0x01ffffffffffffffULL); if (bits != 0) { uint64_t which = 1; for (which = 1; which < 0x0200000000000000 ; which <<= 1) { if (bits & which) { if (!first) fprintf(out, ","); fprintf(out, "CAP_RIGHT(%d, 0x%016llxULL)", (int)ii, (long long unsigned)which); } } } } fprintf(out, "\n"); } void ShowAllCapRights(FILE *out) { int fd; struct rlimit limits; if (getrlimit(RLIMIT_NOFILE, &limits) != 0) { fprintf(out, "Failed to getrlimit for max FDs: errno %d\n", errno); return; } for (fd = 0; fd < (int)limits.rlim_cur; fd++) { if (fcntl(fd, F_GETFD, 0) != 0) { continue; } fprintf(out, "fd %d: ", fd); ShowCapRights(out, fd); } } FORK_TEST(Capability, CapNew) { cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_all; CAP_SET_ALL(&r_all); int cap_fd = dup(STDOUT_FILENO); cap_rights_t rights; CAP_SET_NONE(&rights); EXPECT_OK(cap_rights_get(cap_fd, &rights)); EXPECT_RIGHTS_EQ(&r_all, &rights); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &r_rws)); if (cap_fd < 0) return; int rc = write(cap_fd, "OK!\n", 4); EXPECT_OK(rc); EXPECT_EQ(4, rc); EXPECT_OK(cap_rights_get(cap_fd, &rights)); EXPECT_RIGHTS_EQ(&r_rws, &rights); // dup/dup2 should preserve rights. int cap_dup = dup(cap_fd); EXPECT_OK(cap_dup); EXPECT_OK(cap_rights_get(cap_dup, &rights)); EXPECT_RIGHTS_EQ(&r_rws, &rights); close(cap_dup); EXPECT_OK(dup2(cap_fd, cap_dup)); EXPECT_OK(cap_rights_get(cap_dup, &rights)); EXPECT_RIGHTS_EQ(&r_rws, &rights); close(cap_dup); #ifdef HAVE_DUP3 EXPECT_OK(dup3(cap_fd, cap_dup, 0)); EXPECT_OK(cap_rights_get(cap_dup, &rights)); EXPECT_RIGHTS_EQ(&r_rws, &rights); close(cap_dup); #endif // Try to get a disjoint set of rights in a sub-capability. cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_rsmapchmod; cap_rights_init(&r_rsmapchmod, CAP_READ, CAP_SEEK, CAP_MMAP, CAP_FCHMOD); int cap_cap_fd = dup(cap_fd); EXPECT_OK(cap_cap_fd); EXPECT_NOTCAPABLE(cap_rights_limit(cap_cap_fd, &r_rsmapchmod)); // Dump rights info to stderr (mostly to ensure that Show[All]CapRights() // is working. ShowAllCapRights(stderr); EXPECT_OK(close(cap_fd)); } FORK_TEST(Capability, CapEnter) { EXPECT_EQ(0, cap_enter()); } FORK_TEST(Capability, BasicInterception) { cap_rights_t r_0; cap_rights_init(&r_0, 0); int cap_fd = dup(1); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &r_0)); EXPECT_NOTCAPABLE(write(cap_fd, "", 0)); EXPECT_OK(cap_enter()); // Enter capability mode EXPECT_NOTCAPABLE(write(cap_fd, "", 0)); // Create a new capability which does have write permission cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); int cap_fd2 = dup(1); EXPECT_OK(cap_fd2); EXPECT_OK(cap_rights_limit(cap_fd2, &r_ws)); EXPECT_OK(write(cap_fd2, "", 0)); // Tidy up. if (cap_fd >= 0) close(cap_fd); if (cap_fd2 >= 0) close(cap_fd2); } FORK_TEST_ON(Capability, OpenAtDirectoryTraversal, TmpFile("cap_openat_testfile")) { int dir = open(tmpdir.c_str(), O_RDONLY); EXPECT_OK(dir); cap_enter(); int file = openat(dir, "cap_openat_testfile", O_RDONLY|O_CREAT, 0644); EXPECT_OK(file); // Test that we are confined to /tmp, and cannot // escape using absolute paths or ../. int new_file = openat(dir, "../dev/null", O_RDONLY); EXPECT_EQ(-1, new_file); new_file = openat(dir, "..", O_RDONLY); EXPECT_EQ(-1, new_file); new_file = openat(dir, "/dev/null", O_RDONLY); EXPECT_EQ(-1, new_file); new_file = openat(dir, "/", O_RDONLY); EXPECT_EQ(-1, new_file); // Tidy up. close(file); close(dir); } FORK_TEST_ON(Capability, FileInSync, TmpFile("cap_file_sync")) { int fd = open(TmpFile("cap_file_sync"), O_RDWR|O_CREAT, 0644); EXPECT_OK(fd); const char* message = "Hello capability world"; EXPECT_OK(write(fd, message, strlen(message))); cap_rights_t r_rsstat; cap_rights_init(&r_rsstat, CAP_READ, CAP_SEEK, CAP_FSTAT); int cap_fd = dup(fd); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &r_rsstat)); int cap_cap_fd = dup(cap_fd); EXPECT_OK(cap_cap_fd); EXPECT_OK(cap_rights_limit(cap_cap_fd, &r_rsstat)); EXPECT_OK(cap_enter()); // Enter capability mode. // Changes to one file descriptor affect the others. EXPECT_EQ(1, lseek(fd, 1, SEEK_SET)); EXPECT_EQ(1, lseek(fd, 0, SEEK_CUR)); EXPECT_EQ(1, lseek(cap_fd, 0, SEEK_CUR)); EXPECT_EQ(1, lseek(cap_cap_fd, 0, SEEK_CUR)); EXPECT_EQ(3, lseek(cap_fd, 3, SEEK_SET)); EXPECT_EQ(3, lseek(fd, 0, SEEK_CUR)); EXPECT_EQ(3, lseek(cap_fd, 0, SEEK_CUR)); EXPECT_EQ(3, lseek(cap_cap_fd, 0, SEEK_CUR)); EXPECT_EQ(5, lseek(cap_cap_fd, 5, SEEK_SET)); EXPECT_EQ(5, lseek(fd, 0, SEEK_CUR)); EXPECT_EQ(5, lseek(cap_fd, 0, SEEK_CUR)); EXPECT_EQ(5, lseek(cap_cap_fd, 0, SEEK_CUR)); close(cap_cap_fd); close(cap_fd); close(fd); } // Create a capability on /tmp that does not allow CAP_WRITE, // and check that this restriction is inherited through openat(). FORK_TEST_ON(Capability, Inheritance, TmpFile("cap_openat_write_testfile")) { int dir = open(tmpdir.c_str(), O_RDONLY); EXPECT_OK(dir); cap_rights_t r_rl; cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP); int cap_dir = dup(dir); EXPECT_OK(cap_dir); EXPECT_OK(cap_rights_limit(cap_dir, &r_rl)); const char *filename = "cap_openat_write_testfile"; int file = openat(dir, filename, O_WRONLY|O_CREAT, 0644); EXPECT_OK(file); EXPECT_EQ(5, write(file, "TEST\n", 5)); if (file >= 0) close(file); EXPECT_OK(cap_enter()); file = openat(cap_dir, filename, O_RDONLY); EXPECT_OK(file); cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_get(file, &rights)); EXPECT_RIGHTS_EQ(&r_rl, &rights); if (file >= 0) close(file); file = openat(cap_dir, filename, O_WRONLY|O_APPEND); EXPECT_NOTCAPABLE(file); if (file > 0) close(file); if (dir > 0) close(dir); if (cap_dir > 0) close(cap_dir); } // 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_RIGHT_RESULT(result, rights, ...) do { \ cap_rights_t rights_needed; \ cap_rights_init(&rights_needed, __VA_ARGS__); \ if (cap_rights_contains(&rights, &rights_needed)) { \ EXPECT_OK(result) << std::endl \ << " need: " << rights_needed \ << std::endl \ << " got: " << rights; \ } else { \ EXPECT_EQ(-1, result) << " need: " << rights_needed \ << std::endl \ << " got: "<< rights; \ EXPECT_EQ(ENOTCAPABLE, errno); \ } \ } while (0) #define EXPECT_MMAP_NOTCAPABLE(result) do { \ void *rv = result; \ EXPECT_EQ(MAP_FAILED, rv); \ EXPECT_EQ(ENOTCAPABLE, errno); \ if (rv != MAP_FAILED) munmap(rv, getpagesize()); \ } while (0) #define EXPECT_MMAP_OK(result) do { \ void *rv = result; \ EXPECT_NE(MAP_FAILED, rv) << " with errno " << errno; \ if (rv != MAP_FAILED) munmap(rv, getpagesize()); \ } while (0) // As above, but for the special mmap() case: unmap after successful mmap(). #define CHECK_RIGHT_MMAP_RESULT(result, rights, ...) do { \ cap_rights_t rights_needed; \ cap_rights_init(&rights_needed, __VA_ARGS__); \ if (cap_rights_contains(&rights, &rights_needed)) { \ EXPECT_MMAP_OK(result); \ } else { \ EXPECT_MMAP_NOTCAPABLE(result); \ } \ } while (0) FORK_TEST_ON(Capability, Mmap, TmpFile("cap_mmap_operations")) { int fd = open(TmpFile("cap_mmap_operations"), O_RDWR | O_CREAT, 0644); EXPECT_OK(fd); if (fd < 0) return; cap_rights_t r_0; cap_rights_init(&r_0, 0); cap_rights_t r_mmap; cap_rights_init(&r_mmap, CAP_MMAP); cap_rights_t r_r; cap_rights_init(&r_r, CAP_PREAD); cap_rights_t r_rmmap; cap_rights_init(&r_rmmap, CAP_PREAD, CAP_MMAP); // If we're missing a capability, it will fail. int cap_none = dup(fd); EXPECT_OK(cap_none); EXPECT_OK(cap_rights_limit(cap_none, &r_0)); int cap_mmap = dup(fd); EXPECT_OK(cap_mmap); EXPECT_OK(cap_rights_limit(cap_mmap, &r_mmap)); int cap_read = dup(fd); EXPECT_OK(cap_read); EXPECT_OK(cap_rights_limit(cap_read, &r_r)); int cap_both = dup(fd); EXPECT_OK(cap_both); EXPECT_OK(cap_rights_limit(cap_both, &r_rmmap)); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_MMAP_NOTCAPABLE(mmap(NULL, getpagesize(), PROT_READ, MAP_PRIVATE, cap_none, 0)); EXPECT_MMAP_NOTCAPABLE(mmap(NULL, getpagesize(), PROT_READ, MAP_PRIVATE, cap_mmap, 0)); EXPECT_MMAP_NOTCAPABLE(mmap(NULL, getpagesize(), PROT_READ, MAP_PRIVATE, cap_read, 0)); EXPECT_MMAP_OK(mmap(NULL, getpagesize(), PROT_READ, MAP_PRIVATE, cap_both, 0)); // A call with MAP_ANONYMOUS should succeed without any capability requirements. EXPECT_MMAP_OK(mmap(NULL, getpagesize(), PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)); EXPECT_OK(close(cap_both)); EXPECT_OK(close(cap_read)); EXPECT_OK(close(cap_mmap)); EXPECT_OK(close(cap_none)); EXPECT_OK(close(fd)); } // Given a file descriptor, create a capability with specific rights and // make sure only those rights work. #define TRY_FILE_OPS(fd, ...) do { \ cap_rights_t rights; \ cap_rights_init(&rights, __VA_ARGS__); \ TryFileOps((fd), rights); \ } while (0) static void TryFileOps(int fd, cap_rights_t rights) { int cap_fd = dup(fd); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); if (cap_fd < 0) return; cap_rights_t erights; EXPECT_OK(cap_rights_get(cap_fd, &erights)); EXPECT_RIGHTS_EQ(&rights, &erights); // Check creation of a capability from a capability. int cap_cap_fd = dup(cap_fd); EXPECT_OK(cap_cap_fd); EXPECT_OK(cap_rights_limit(cap_cap_fd, &rights)); EXPECT_NE(cap_fd, cap_cap_fd); EXPECT_OK(cap_rights_get(cap_cap_fd, &erights)); EXPECT_RIGHTS_EQ(&rights, &erights); close(cap_cap_fd); char ch; CHECK_RIGHT_RESULT(read(cap_fd, &ch, sizeof(ch)), rights, CAP_READ, CAP_SEEK_ASWAS); ssize_t len1 = pread(cap_fd, &ch, sizeof(ch), 0); CHECK_RIGHT_RESULT(len1, rights, CAP_PREAD); ssize_t len2 = pread(cap_fd, &ch, sizeof(ch), 0); CHECK_RIGHT_RESULT(len2, rights, CAP_PREAD); EXPECT_EQ(len1, len2); CHECK_RIGHT_RESULT(write(cap_fd, &ch, sizeof(ch)), rights, CAP_WRITE, CAP_SEEK_ASWAS); CHECK_RIGHT_RESULT(pwrite(cap_fd, &ch, sizeof(ch), 0), rights, CAP_PWRITE); CHECK_RIGHT_RESULT(lseek(cap_fd, 0, SEEK_SET), rights, CAP_SEEK); #ifdef HAVE_CHFLAGS // Note: this is not expected to work over NFS. struct statfs sf; EXPECT_OK(fstatfs(fd, &sf)); bool is_nfs = (strncmp("nfs", sf.f_fstypename, sizeof(sf.f_fstypename)) == 0); if (!is_nfs) { CHECK_RIGHT_RESULT(fchflags(cap_fd, UF_NODUMP), rights, CAP_FCHFLAGS); } #endif CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_NONE, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_R); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_W); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_EXEC, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_X); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_RW); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_READ | PROT_EXEC, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_RX); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_EXEC | PROT_WRITE, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_WX); CHECK_RIGHT_MMAP_RESULT(mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, cap_fd, 0), rights, CAP_MMAP_RWX); CHECK_RIGHT_RESULT(fsync(cap_fd), rights, CAP_FSYNC); #ifdef HAVE_SYNC_FILE_RANGE CHECK_RIGHT_RESULT(sync_file_range(cap_fd, 0, 1, 0), rights, CAP_FSYNC, CAP_SEEK); #endif int rc = fcntl(cap_fd, F_GETFL); CHECK_RIGHT_RESULT(rc, rights, CAP_FCNTL); rc = fcntl(cap_fd, F_SETFL, rc); CHECK_RIGHT_RESULT(rc, rights, CAP_FCNTL); CHECK_RIGHT_RESULT(fchown(cap_fd, -1, -1), rights, CAP_FCHOWN); CHECK_RIGHT_RESULT(fchmod(cap_fd, 0644), rights, CAP_FCHMOD); CHECK_RIGHT_RESULT(flock(cap_fd, LOCK_SH), rights, CAP_FLOCK); CHECK_RIGHT_RESULT(flock(cap_fd, LOCK_UN), rights, CAP_FLOCK); CHECK_RIGHT_RESULT(ftruncate(cap_fd, 0), rights, CAP_FTRUNCATE); struct stat sb; CHECK_RIGHT_RESULT(fstat(cap_fd, &sb), rights, CAP_FSTAT); struct statfs cap_sf; CHECK_RIGHT_RESULT(fstatfs(cap_fd, &cap_sf), rights, CAP_FSTATFS); #ifdef HAVE_FPATHCONF CHECK_RIGHT_RESULT(fpathconf(cap_fd, _PC_NAME_MAX), rights, CAP_FPATHCONF); #endif CHECK_RIGHT_RESULT(futimes(cap_fd, NULL), rights, CAP_FUTIMES); struct pollfd pollfd; pollfd.fd = cap_fd; pollfd.events = POLLIN | POLLERR | POLLHUP; pollfd.revents = 0; int ret = poll(&pollfd, 1, 0); if (cap_rights_is_set(&rights, CAP_EVENT)) { EXPECT_OK(ret); } else { EXPECT_NE(0, (pollfd.revents & POLLNVAL)); } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100; fd_set rset; FD_ZERO(&rset); FD_SET(cap_fd, &rset); fd_set wset; FD_ZERO(&wset); FD_SET(cap_fd, &wset); ret = select(cap_fd+1, &rset, &wset, NULL, &tv); if (cap_rights_is_set(&rights, CAP_EVENT)) { EXPECT_OK(ret); } else { EXPECT_NOTCAPABLE(ret); } // TODO(FreeBSD): kqueue EXPECT_OK(close(cap_fd)); } FORK_TEST_ON(Capability, Operations, TmpFile("cap_fd_operations")) { int fd = open(TmpFile("cap_fd_operations"), O_RDWR | O_CREAT, 0644); EXPECT_OK(fd); if (fd < 0) return; EXPECT_OK(cap_enter()); // Enter capability mode. // Try a variety of different combinations of rights - a full // enumeration is too large (2^N with N~30+) to perform. TRY_FILE_OPS(fd, CAP_READ); TRY_FILE_OPS(fd, CAP_PREAD); TRY_FILE_OPS(fd, CAP_WRITE); TRY_FILE_OPS(fd, CAP_PWRITE); TRY_FILE_OPS(fd, CAP_READ, CAP_WRITE); TRY_FILE_OPS(fd, CAP_PREAD, CAP_PWRITE); TRY_FILE_OPS(fd, CAP_SEEK); TRY_FILE_OPS(fd, CAP_FCHFLAGS); TRY_FILE_OPS(fd, CAP_IOCTL); TRY_FILE_OPS(fd, CAP_FSTAT); TRY_FILE_OPS(fd, CAP_MMAP); TRY_FILE_OPS(fd, CAP_MMAP_R); TRY_FILE_OPS(fd, CAP_MMAP_W); TRY_FILE_OPS(fd, CAP_MMAP_X); TRY_FILE_OPS(fd, CAP_MMAP_RW); TRY_FILE_OPS(fd, CAP_MMAP_RX); TRY_FILE_OPS(fd, CAP_MMAP_WX); TRY_FILE_OPS(fd, CAP_MMAP_RWX); TRY_FILE_OPS(fd, CAP_FCNTL); TRY_FILE_OPS(fd, CAP_EVENT); TRY_FILE_OPS(fd, CAP_FSYNC); TRY_FILE_OPS(fd, CAP_FCHOWN); TRY_FILE_OPS(fd, CAP_FCHMOD); TRY_FILE_OPS(fd, CAP_FTRUNCATE); TRY_FILE_OPS(fd, CAP_FLOCK); TRY_FILE_OPS(fd, CAP_FSTATFS); TRY_FILE_OPS(fd, CAP_FPATHCONF); TRY_FILE_OPS(fd, CAP_FUTIMES); TRY_FILE_OPS(fd, CAP_ACL_GET); TRY_FILE_OPS(fd, CAP_ACL_SET); TRY_FILE_OPS(fd, CAP_ACL_DELETE); TRY_FILE_OPS(fd, CAP_ACL_CHECK); TRY_FILE_OPS(fd, CAP_EXTATTR_GET); TRY_FILE_OPS(fd, CAP_EXTATTR_SET); TRY_FILE_OPS(fd, CAP_EXTATTR_DELETE); TRY_FILE_OPS(fd, CAP_EXTATTR_LIST); TRY_FILE_OPS(fd, CAP_MAC_GET); TRY_FILE_OPS(fd, CAP_MAC_SET); // Socket-specific. TRY_FILE_OPS(fd, CAP_GETPEERNAME); TRY_FILE_OPS(fd, CAP_GETSOCKNAME); TRY_FILE_OPS(fd, CAP_ACCEPT); close(fd); } #define TRY_DIR_OPS(dfd, ...) do { \ cap_rights_t rights; \ cap_rights_init(&rights, __VA_ARGS__); \ TryDirOps((dfd), rights); \ } while (0) static void TryDirOps(int dirfd, cap_rights_t rights) { cap_rights_t erights; int dfd_cap = dup(dirfd); EXPECT_OK(dfd_cap); EXPECT_OK(cap_rights_limit(dfd_cap, &rights)); EXPECT_OK(cap_rights_get(dfd_cap, &erights)); EXPECT_RIGHTS_EQ(&rights, &erights); int rc = openat(dfd_cap, "cap_create", O_CREAT | O_RDONLY, 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_CREATE, CAP_READ, CAP_LOOKUP); if (rc >= 0) { EXPECT_OK(close(rc)); EXPECT_OK(unlinkat(dirfd, "cap_create", 0)); } rc = openat(dfd_cap, "cap_create", O_CREAT | O_WRONLY | O_APPEND, 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_CREATE, CAP_WRITE, CAP_LOOKUP); if (rc >= 0) { EXPECT_OK(close(rc)); EXPECT_OK(unlinkat(dirfd, "cap_create", 0)); } rc = openat(dfd_cap, "cap_create", O_CREAT | O_RDWR | O_APPEND, 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_CREATE, CAP_READ, CAP_WRITE, CAP_LOOKUP); if (rc >= 0) { EXPECT_OK(close(rc)); EXPECT_OK(unlinkat(dirfd, "cap_create", 0)); } rc = openat(dirfd, "cap_faccess", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = faccessat(dfd_cap, "cap_faccess", F_OK, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_FSTAT, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_faccess", 0)); rc = openat(dirfd, "cap_fsync", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_FSYNC | O_WRONLY | O_APPEND); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDWR | O_APPEND); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_SYNC | O_WRONLY | O_APPEND); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDWR | O_APPEND); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } EXPECT_OK(unlinkat(dirfd, "cap_fsync", 0)); rc = openat(dirfd, "cap_ftruncate", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_RDONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FTRUNCATE, CAP_READ, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_WRONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FTRUNCATE, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_ftruncate", O_TRUNC | O_RDWR); CHECK_RIGHT_RESULT(rc, rights, CAP_FTRUNCATE, CAP_READ, CAP_WRITE, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } EXPECT_OK(unlinkat(dirfd, "cap_ftruncate", 0)); rc = openat(dfd_cap, "cap_create", O_CREAT | O_WRONLY, 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_CREATE, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); if (rc >= 0) { EXPECT_OK(close(rc)); EXPECT_OK(unlinkat(dirfd, "cap_create", 0)); } rc = openat(dfd_cap, "cap_create", O_CREAT | O_RDWR, 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_CREATE, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); if (rc >= 0) { EXPECT_OK(close(rc)); EXPECT_OK(unlinkat(dirfd, "cap_create", 0)); } rc = openat(dirfd, "cap_fsync", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = openat(dfd_cap, "cap_fsync", O_FSYNC | O_WRONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_FSYNC | O_RDWR); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_SYNC | O_WRONLY); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } rc = openat(dfd_cap, "cap_fsync", O_SYNC | O_RDWR); CHECK_RIGHT_RESULT(rc, rights, CAP_FSYNC, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(close(rc)); + if (rc >= 0) { + EXPECT_OK(close(rc)); + } EXPECT_OK(unlinkat(dirfd, "cap_fsync", 0)); #ifdef HAVE_CHFLAGSAT rc = openat(dirfd, "cap_chflagsat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = chflagsat(dfd_cap, "cap_chflagsat", UF_NODUMP, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_CHFLAGSAT, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_chflagsat", 0)); #endif rc = openat(dirfd, "cap_fchownat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = fchownat(dfd_cap, "cap_fchownat", -1, -1, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_FCHOWN, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_fchownat", 0)); rc = openat(dirfd, "cap_fchmodat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = fchmodat(dfd_cap, "cap_fchmodat", 0600, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_FCHMOD, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_fchmodat", 0)); rc = openat(dirfd, "cap_fstatat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); struct stat sb; rc = fstatat(dfd_cap, "cap_fstatat", &sb, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_FSTAT, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_fstatat", 0)); rc = openat(dirfd, "cap_futimesat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = futimesat(dfd_cap, "cap_futimesat", NULL); CHECK_RIGHT_RESULT(rc, rights, CAP_FUTIMES, CAP_LOOKUP); EXPECT_OK(unlinkat(dirfd, "cap_futimesat", 0)); // For linkat(2), need: // - CAP_LINKAT_SOURCE on source // - CAP_LINKAT_TARGET on destination. rc = openat(dirfd, "cap_linkat_src", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = linkat(dirfd, "cap_linkat_src", dfd_cap, "cap_linkat_dst", 0); CHECK_RIGHT_RESULT(rc, rights, CAP_LINKAT_TARGET); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_linkat_dst", 0)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_linkat_dst", 0)); + } rc = linkat(dfd_cap, "cap_linkat_src", dirfd, "cap_linkat_dst", 0); CHECK_RIGHT_RESULT(rc, rights, CAP_LINKAT_SOURCE); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_linkat_dst", 0)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_linkat_dst", 0)); + } EXPECT_OK(unlinkat(dirfd, "cap_linkat_src", 0)); rc = mkdirat(dfd_cap, "cap_mkdirat", 0700); CHECK_RIGHT_RESULT(rc, rights, CAP_MKDIRAT, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_mkdirat", AT_REMOVEDIR)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_mkdirat", AT_REMOVEDIR)); + } #ifdef HAVE_MKFIFOAT rc = mkfifoat(dfd_cap, "cap_mkfifoat", 0600); CHECK_RIGHT_RESULT(rc, rights, CAP_MKFIFOAT, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_mkfifoat", 0)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_mkfifoat", 0)); + } #endif if (getuid() == 0) { rc = mknodat(dfd_cap, "cap_mknodat", S_IFCHR | 0600, 0); CHECK_RIGHT_RESULT(rc, rights, CAP_MKNODAT, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_mknodat", 0)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_mknodat", 0)); + } } // For renameat(2), need: // - CAP_RENAMEAT_SOURCE on source // - CAP_RENAMEAT_TARGET on destination. rc = openat(dirfd, "cap_renameat_src", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = renameat(dirfd, "cap_renameat_src", dfd_cap, "cap_renameat_dst"); CHECK_RIGHT_RESULT(rc, rights, CAP_RENAMEAT_TARGET); if (rc >= 0) { EXPECT_OK(unlinkat(dirfd, "cap_renameat_dst", 0)); } else { EXPECT_OK(unlinkat(dirfd, "cap_renameat_src", 0)); } rc = openat(dirfd, "cap_renameat_src", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = renameat(dfd_cap, "cap_renameat_src", dirfd, "cap_renameat_dst"); CHECK_RIGHT_RESULT(rc, rights, CAP_RENAMEAT_SOURCE); if (rc >= 0) { EXPECT_OK(unlinkat(dirfd, "cap_renameat_dst", 0)); } else { EXPECT_OK(unlinkat(dirfd, "cap_renameat_src", 0)); } rc = symlinkat("test", dfd_cap, "cap_symlinkat"); CHECK_RIGHT_RESULT(rc, rights, CAP_SYMLINKAT, CAP_LOOKUP); - if (rc >= 0) EXPECT_OK(unlinkat(dirfd, "cap_symlinkat", 0)); + if (rc >= 0) { + EXPECT_OK(unlinkat(dirfd, "cap_symlinkat", 0)); + } rc = openat(dirfd, "cap_unlinkat", O_CREAT, 0600); EXPECT_OK(rc); EXPECT_OK(close(rc)); rc = unlinkat(dfd_cap, "cap_unlinkat", 0); CHECK_RIGHT_RESULT(rc, rights, CAP_UNLINKAT, CAP_LOOKUP); unlinkat(dirfd, "cap_unlinkat", 0); EXPECT_OK(mkdirat(dirfd, "cap_unlinkat", 0700)); rc = unlinkat(dfd_cap, "cap_unlinkat", AT_REMOVEDIR); CHECK_RIGHT_RESULT(rc, rights, CAP_UNLINKAT, CAP_LOOKUP); unlinkat(dirfd, "cap_unlinkat", AT_REMOVEDIR); EXPECT_OK(close(dfd_cap)); } void DirOperationsTest(int extra) { int rc = mkdir(TmpFile("cap_dirops"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; int dfd = open(TmpFile("cap_dirops"), O_RDONLY | O_DIRECTORY | extra); EXPECT_OK(dfd); int tmpfd = open(tmpdir.c_str(), O_RDONLY | O_DIRECTORY); EXPECT_OK(tmpfd); EXPECT_OK(cap_enter()); // Enter capability mode. TRY_DIR_OPS(dfd, CAP_LINKAT_SOURCE); TRY_DIR_OPS(dfd, CAP_LINKAT_TARGET); TRY_DIR_OPS(dfd, CAP_CREATE, CAP_READ, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_CREATE, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_CREATE, CAP_READ, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FSYNC, CAP_READ, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FSYNC, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FSYNC, CAP_READ, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FTRUNCATE, CAP_READ, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FTRUNCATE, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FTRUNCATE, CAP_READ, CAP_WRITE, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FCHOWN, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FCHMOD, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FSTAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_FUTIMES, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_MKDIRAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_MKFIFOAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_MKNODAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_SYMLINKAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_UNLINKAT, CAP_LOOKUP); // Rename needs CAP_RENAMEAT_SOURCE on source directory and // CAP_RENAMEAT_TARGET on destination directory. TRY_DIR_OPS(dfd, CAP_RENAMEAT_SOURCE, CAP_UNLINKAT, CAP_LOOKUP); TRY_DIR_OPS(dfd, CAP_RENAMEAT_TARGET, CAP_UNLINKAT, CAP_LOOKUP); EXPECT_OK(unlinkat(tmpfd, "cap_dirops", AT_REMOVEDIR)); EXPECT_OK(close(tmpfd)); EXPECT_OK(close(dfd)); } FORK_TEST(Capability, DirOperations) { DirOperationsTest(0); } #ifdef O_PATH FORK_TEST(Capability, PathDirOperations) { // Make the dfd in the test a path-only file descriptor. DirOperationsTest(O_PATH); } #endif static void TryReadWrite(int cap_fd) { char buffer[64]; EXPECT_OK(read(cap_fd, buffer, sizeof(buffer))); int rc = write(cap_fd, "", 0); EXPECT_EQ(-1, rc); EXPECT_EQ(ENOTCAPABLE, errno); } FORK_TEST_ON(Capability, SocketTransfer, TmpFile("cap_fd_transfer")) { int sock_fds[2]; EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds)); struct msghdr mh; mh.msg_name = NULL; // No address needed mh.msg_namelen = 0; char buffer1[1024]; struct iovec iov[1]; iov[0].iov_base = buffer1; iov[0].iov_len = sizeof(buffer1); mh.msg_iov = iov; mh.msg_iovlen = 1; char buffer2[1024]; mh.msg_control = buffer2; mh.msg_controllen = sizeof(buffer2); struct cmsghdr *cmptr; cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); pid_t child = fork(); if (child == 0) { // Child: enter cap mode EXPECT_OK(cap_enter()); // Child: wait to receive FD over socket int rc = recvmsg(sock_fds[0], &mh, 0); EXPECT_OK(rc); EXPECT_LE(CMSG_LEN(sizeof(int)), mh.msg_controllen); cmptr = CMSG_FIRSTHDR(&mh); int cap_fd = *(int*)CMSG_DATA(cmptr); EXPECT_EQ(CMSG_LEN(sizeof(int)), cmptr->cmsg_len); cmptr = CMSG_NXTHDR(&mh, cmptr); EXPECT_TRUE(cmptr == NULL); // Child: confirm we can do the right operations on the capability cap_rights_t rights; EXPECT_OK(cap_rights_get(cap_fd, &rights)); EXPECT_RIGHTS_EQ(&r_rs, &rights); TryReadWrite(cap_fd); // Child: wait for a normal read int val; read(sock_fds[0], &val, sizeof(val)); exit(0); } int fd = open(TmpFile("cap_fd_transfer"), O_RDWR | O_CREAT, 0644); EXPECT_OK(fd); if (fd < 0) return; int cap_fd = dup(fd); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &r_rs)); EXPECT_OK(cap_enter()); // Enter capability mode. // Confirm we can do the right operations on the capability TryReadWrite(cap_fd); // Send the file descriptor over the pipe to the sub-process mh.msg_controllen = CMSG_LEN(sizeof(int)); cmptr = CMSG_FIRSTHDR(&mh); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; cmptr->cmsg_len = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cmptr) = cap_fd; buffer1[0] = 0; iov[0].iov_len = 1; sleep(3); int rc = sendmsg(sock_fds[1], &mh, 0); EXPECT_OK(rc); sleep(1); // Ensure subprocess runs int zero = 0; write(sock_fds[1], &zero, sizeof(zero)); } TEST(Capability, SyscallAt) { int rc = mkdir(TmpFile("cap_at_topdir"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; cap_rights_t r_all; cap_rights_init(&r_all, CAP_READ, CAP_LOOKUP, CAP_MKNODAT, CAP_UNLINKAT, CAP_MKDIRAT, CAP_MKFIFOAT); cap_rights_t r_no_unlink; cap_rights_init(&r_no_unlink, CAP_READ, CAP_LOOKUP, CAP_MKDIRAT, CAP_MKFIFOAT); cap_rights_t r_no_mkdir; cap_rights_init(&r_no_mkdir, CAP_READ, CAP_LOOKUP, CAP_UNLINKAT, CAP_MKFIFOAT); cap_rights_t r_no_mkfifo; cap_rights_init(&r_no_mkfifo, CAP_READ, CAP_LOOKUP, CAP_UNLINKAT, CAP_MKDIRAT); cap_rights_t r_no_mknod; cap_rights_init(&r_no_mknod, CAP_READ, CAP_LOOKUP, CAP_UNLINKAT, CAP_MKDIRAT); cap_rights_t r_create; cap_rights_init(&r_create, CAP_READ, CAP_LOOKUP, CAP_CREATE); cap_rights_t r_bind; cap_rights_init(&r_bind, CAP_READ, CAP_LOOKUP, CAP_BIND); int dfd = open(TmpFile("cap_at_topdir"), O_RDONLY); EXPECT_OK(dfd); int cap_dfd_all = dup(dfd); EXPECT_OK(cap_dfd_all); EXPECT_OK(cap_rights_limit(cap_dfd_all, &r_all)); int cap_dfd_no_unlink = dup(dfd); EXPECT_OK(cap_dfd_no_unlink); EXPECT_OK(cap_rights_limit(cap_dfd_no_unlink, &r_no_unlink)); int cap_dfd_no_mkdir = dup(dfd); EXPECT_OK(cap_dfd_no_mkdir); EXPECT_OK(cap_rights_limit(cap_dfd_no_mkdir, &r_no_mkdir)); int cap_dfd_no_mkfifo = dup(dfd); EXPECT_OK(cap_dfd_no_mkfifo); EXPECT_OK(cap_rights_limit(cap_dfd_no_mkfifo, &r_no_mkfifo)); int cap_dfd_no_mknod = dup(dfd); EXPECT_OK(cap_dfd_no_mknod); EXPECT_OK(cap_rights_limit(cap_dfd_no_mknod, &r_no_mknod)); int cap_dfd_create = dup(dfd); EXPECT_OK(cap_dfd_create); EXPECT_OK(cap_rights_limit(cap_dfd_create, &r_create)); int cap_dfd_bind = dup(dfd); EXPECT_OK(cap_dfd_bind); EXPECT_OK(cap_rights_limit(cap_dfd_bind, &r_bind)); // Need CAP_MKDIRAT to mkdirat(2). EXPECT_NOTCAPABLE(mkdirat(cap_dfd_no_mkdir, "cap_subdir", 0755)); rmdir(TmpFile("cap_at_topdir/cap_subdir")); EXPECT_OK(mkdirat(cap_dfd_all, "cap_subdir", 0755)); // Need CAP_UNLINKAT to unlinkat(dfd, name, AT_REMOVEDIR). EXPECT_NOTCAPABLE(unlinkat(cap_dfd_no_unlink, "cap_subdir", AT_REMOVEDIR)); EXPECT_OK(unlinkat(cap_dfd_all, "cap_subdir", AT_REMOVEDIR)); rmdir(TmpFile("cap_at_topdir/cap_subdir")); // Need CAP_MKFIFOAT to mkfifoat(2). EXPECT_NOTCAPABLE(mkfifoat(cap_dfd_no_mkfifo, "cap_fifo", 0755)); unlink(TmpFile("cap_at_topdir/cap_fifo")); EXPECT_OK(mkfifoat(cap_dfd_all, "cap_fifo", 0755)); unlink(TmpFile("cap_at_topdir/cap_fifo")); #ifdef HAVE_MKNOD_REG // Need CAP_CREATE to create a regular file with mknodat(2). EXPECT_NOTCAPABLE(mknodat(cap_dfd_all, "cap_regular", S_IFREG|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_regular")); EXPECT_OK(mknodat(cap_dfd_create, "cap_regular", S_IFREG|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_regular")); #endif #ifdef HAVE_MKNOD_SOCKET // Need CAP_BIND to create a UNIX domain socket with mknodat(2). EXPECT_NOTCAPABLE(mknodat(cap_dfd_all, "cap_socket", S_IFSOCK|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_socket")); EXPECT_OK(mknodat(cap_dfd_bind, "cap_socket", S_IFSOCK|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_socket")); #endif if (getuid() == 0) { // Need CAP_MKNODAT to mknodat(2) a device EXPECT_NOTCAPABLE(mknodat(cap_dfd_no_mknod, "cap_device", S_IFCHR|0755, makedev(99, 123))); unlink(TmpFile("cap_at_topdir/cap_device")); EXPECT_OK(mknodat(cap_dfd_all, "cap_device", S_IFCHR|0755, makedev(99, 123))); unlink(TmpFile("cap_at_topdir/cap_device")); // Need CAP_MKFIFOAT to mknodat(2) for a FIFO. EXPECT_NOTCAPABLE(mknodat(cap_dfd_no_mkfifo, "cap_fifo", S_IFIFO|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_fifo")); EXPECT_OK(mknodat(cap_dfd_all, "cap_fifo", S_IFIFO|0755, 0)); unlink(TmpFile("cap_at_topdir/cap_fifo")); } else { TEST_SKIPPED("requires root (partial)"); } close(cap_dfd_all); close(cap_dfd_no_mknod); close(cap_dfd_no_mkfifo); close(cap_dfd_no_mkdir); close(cap_dfd_no_unlink); close(cap_dfd_create); close(cap_dfd_bind); close(dfd); // Tidy up. rmdir(TmpFile("cap_at_topdir")); } FORK_TEST_ON(Capability, ExtendedAttributes, TmpFile("cap_extattr")) { int fd = open(TmpFile("cap_extattr"), O_RDONLY|O_CREAT, 0644); EXPECT_OK(fd); char buffer[1024]; int rc = fgetxattr_(fd, "user.capsicumtest", buffer, sizeof(buffer)); if (rc < 0 && errno == ENOTSUP) { // Need user_xattr mount option for non-root users on Linux TEST_SKIPPED("/tmp doesn't support extended attributes"); close(fd); return; } cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_xlist; cap_rights_init(&r_xlist, CAP_EXTATTR_LIST); cap_rights_t r_xget; cap_rights_init(&r_xget, CAP_EXTATTR_GET); cap_rights_t r_xset; cap_rights_init(&r_xset, CAP_EXTATTR_SET); cap_rights_t r_xdel; cap_rights_init(&r_xdel, CAP_EXTATTR_DELETE); int cap = dup(fd); EXPECT_OK(cap); EXPECT_OK(cap_rights_limit(cap, &r_rws)); int cap_xlist = dup(fd); EXPECT_OK(cap_xlist); EXPECT_OK(cap_rights_limit(cap_xlist, &r_xlist)); int cap_xget = dup(fd); EXPECT_OK(cap_xget); EXPECT_OK(cap_rights_limit(cap_xget, &r_xget)); int cap_xset = dup(fd); EXPECT_OK(cap_xset); EXPECT_OK(cap_rights_limit(cap_xset, &r_xset)); int cap_xdel = dup(fd); EXPECT_OK(cap_xdel); EXPECT_OK(cap_rights_limit(cap_xdel, &r_xdel)); const char* value = "capsicum"; int len = strlen(value) + 1; EXPECT_NOTCAPABLE(fsetxattr_(cap, "user.capsicumtest", value, len, 0)); EXPECT_NOTCAPABLE(fsetxattr_(cap_xlist, "user.capsicumtest", value, len, 0)); EXPECT_NOTCAPABLE(fsetxattr_(cap_xget, "user.capsicumtest", value, len, 0)); EXPECT_NOTCAPABLE(fsetxattr_(cap_xdel, "user.capsicumtest", value, len, 0)); EXPECT_OK(fsetxattr_(cap_xset, "user.capsicumtest", value, len, 0)); EXPECT_NOTCAPABLE(flistxattr_(cap, buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(flistxattr_(cap_xget, buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(flistxattr_(cap_xset, buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(flistxattr_(cap_xdel, buffer, sizeof(buffer))); EXPECT_OK(flistxattr_(cap_xlist, buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(fgetxattr_(cap, "user.capsicumtest", buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(fgetxattr_(cap_xlist, "user.capsicumtest", buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(fgetxattr_(cap_xset, "user.capsicumtest", buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(fgetxattr_(cap_xdel, "user.capsicumtest", buffer, sizeof(buffer))); EXPECT_OK(fgetxattr_(cap_xget, "user.capsicumtest", buffer, sizeof(buffer))); EXPECT_NOTCAPABLE(fremovexattr_(cap, "user.capsicumtest")); EXPECT_NOTCAPABLE(fremovexattr_(cap_xlist, "user.capsicumtest")); EXPECT_NOTCAPABLE(fremovexattr_(cap_xget, "user.capsicumtest")); EXPECT_NOTCAPABLE(fremovexattr_(cap_xset, "user.capsicumtest")); EXPECT_OK(fremovexattr_(cap_xdel, "user.capsicumtest")); close(cap_xdel); close(cap_xset); close(cap_xget); close(cap_xlist); close(cap); close(fd); } TEST(Capability, PipeUnseekable) { int fds[2]; EXPECT_OK(pipe(fds)); // Some programs detect pipes by calling seek() and getting ESPIPE. EXPECT_EQ(-1, lseek(fds[0], 0, SEEK_SET)); EXPECT_EQ(ESPIPE, errno); cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK); EXPECT_OK(cap_rights_limit(fds[0], &rights)); EXPECT_EQ(-1, lseek(fds[0], 0, SEEK_SET)); EXPECT_EQ(ESPIPE, errno); // Remove CAP_SEEK and see if ENOTCAPABLE trumps ESPIPE. cap_rights_init(&rights, CAP_READ, CAP_WRITE); EXPECT_OK(cap_rights_limit(fds[0], &rights)); EXPECT_EQ(-1, lseek(fds[0], 0, SEEK_SET)); EXPECT_EQ(ENOTCAPABLE, errno); // TODO(drysdale): in practical terms it might be nice if ESPIPE trumped ENOTCAPABLE. // EXPECT_EQ(ESPIPE, errno); close(fds[0]); close(fds[1]); } TEST(Capability, NoBypassDAC) { REQUIRE_ROOT(); int fd = open(TmpFile("cap_root_owned"), O_RDONLY|O_CREAT, 0644); EXPECT_OK(fd); cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FCHMOD, CAP_FSTAT); EXPECT_OK(cap_rights_limit(fd, &rights)); pid_t child = fork(); if (child == 0) { // Child: change uid to a lesser being setuid(other_uid); // Attempt to fchmod the file, and fail. // Having CAP_FCHMOD doesn't bypass the need to comply with DAC policy. int rc = fchmod(fd, 0666); EXPECT_EQ(-1, rc); EXPECT_EQ(EPERM, errno); exit(HasFailure()); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; EXPECT_EQ(0, WEXITSTATUS(status)); struct stat info; EXPECT_OK(fstat(fd, &info)); EXPECT_EQ((mode_t)(S_IFREG|0644), info.st_mode); close(fd); unlink(TmpFile("cap_root_owned")); } Index: projects/capsicum-test/contrib/capsicum-test/capmode.cc =================================================================== --- projects/capsicum-test/contrib/capsicum-test/capmode.cc (revision 345423) +++ projects/capsicum-test/contrib/capsicum-test/capmode.cc (revision 345424) @@ -1,650 +1,654 @@ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" // Test fixture that opens (and closes) a bunch of files. class WithFiles : public ::testing::Test { public: WithFiles() : fd_file_(open(TmpFile("cap_capmode"), O_RDWR|O_CREAT, 0644)), fd_close_(open("/dev/null", O_RDWR)), fd_dir_(open(tmpdir.c_str(), O_RDONLY)), fd_socket_(socket(PF_INET, SOCK_DGRAM, 0)), fd_tcp_socket_(socket(PF_INET, SOCK_STREAM, 0)) { EXPECT_OK(fd_file_); EXPECT_OK(fd_close_); EXPECT_OK(fd_dir_); EXPECT_OK(fd_socket_); EXPECT_OK(fd_tcp_socket_); } ~WithFiles() { if (fd_tcp_socket_ >= 0) close(fd_tcp_socket_); if (fd_socket_ >= 0) close(fd_socket_); if (fd_dir_ >= 0) close(fd_dir_); if (fd_close_ >= 0) close(fd_close_); if (fd_file_ >= 0) close(fd_file_); unlink(TmpFile("cap_capmode")); } protected: int fd_file_; int fd_close_; int fd_dir_; int fd_socket_; int fd_tcp_socket_; }; FORK_TEST_F(WithFiles, DisallowedFileSyscalls) { unsigned int mode = -1; EXPECT_OK(cap_getmode(&mode)); EXPECT_EQ(0, (int)mode); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(cap_getmode(&mode)); EXPECT_EQ(1, (int)mode); // System calls that are not permitted in capability mode. EXPECT_CAPMODE(access(TmpFile("cap_capmode_access"), F_OK)); EXPECT_CAPMODE(acct(TmpFile("cap_capmode_acct"))); EXPECT_CAPMODE(chdir(TmpFile("cap_capmode_chdir"))); #ifdef HAVE_CHFLAGS EXPECT_CAPMODE(chflags(TmpFile("cap_capmode_chflags"), UF_NODUMP)); #endif EXPECT_CAPMODE(chmod(TmpFile("cap_capmode_chmod"), 0644)); EXPECT_CAPMODE(chown(TmpFile("cap_capmode_chown"), -1, -1)); EXPECT_CAPMODE(chroot(TmpFile("cap_capmode_chroot"))); EXPECT_CAPMODE(creat(TmpFile("cap_capmode_creat"), 0644)); EXPECT_CAPMODE(fchdir(fd_dir_)); #ifdef HAVE_GETFSSTAT struct statfs statfs; EXPECT_CAPMODE(getfsstat(&statfs, sizeof(statfs), MNT_NOWAIT)); #endif EXPECT_CAPMODE(link(TmpFile("foo"), TmpFile("bar"))); struct stat sb; EXPECT_CAPMODE(lstat(TmpFile("cap_capmode_lstat"), &sb)); EXPECT_CAPMODE(mknod(TmpFile("capmode_mknod"), 0644 | S_IFIFO, 0)); EXPECT_CAPMODE(bogus_mount_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); char buf[64]; EXPECT_CAPMODE(readlink(TmpFile("cap_capmode_readlink"), buf, sizeof(buf))); #ifdef HAVE_REVOKE EXPECT_CAPMODE(revoke(TmpFile("cap_capmode_revoke"))); #endif EXPECT_CAPMODE(stat(TmpFile("cap_capmode_stat"), &sb)); EXPECT_CAPMODE(symlink(TmpFile("cap_capmode_symlink_from"), TmpFile("cap_capmode_symlink_to"))); EXPECT_CAPMODE(unlink(TmpFile("cap_capmode_unlink"))); EXPECT_CAPMODE(umount2("/not_mounted", 0)); } FORK_TEST_F(WithFiles, DisallowedSocketSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. // System calls that are not permitted in capability mode. struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = htonl(INADDR_ANY); EXPECT_CAPMODE(bind_(fd_socket_, (sockaddr*)&addr, sizeof(addr))); addr.sin_family = AF_INET; addr.sin_port = 53; addr.sin_addr.s_addr = htonl(0x08080808); EXPECT_CAPMODE(connect_(fd_tcp_socket_, (sockaddr*)&addr, sizeof(addr))); } FORK_TEST_F(WithFiles, AllowedFileSyscalls) { int rc; EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(close(fd_close_)); fd_close_ = -1; int fd_dup = dup(fd_file_); EXPECT_OK(fd_dup); EXPECT_OK(dup2(fd_file_, fd_dup)); #ifdef HAVE_DUP3 EXPECT_OK(dup3(fd_file_, fd_dup, 0)); #endif if (fd_dup >= 0) close(fd_dup); struct stat sb; EXPECT_OK(fstat(fd_file_, &sb)); EXPECT_OK(lseek(fd_file_, 0, SEEK_SET)); char ch; EXPECT_OK(read(fd_file_, &ch, sizeof(ch))); EXPECT_OK(write(fd_file_, &ch, sizeof(ch))); #ifdef HAVE_CHFLAGS rc = fchflags(fd_file_, UF_NODUMP); - if (rc < 0) EXPECT_NE(ECAPMODE, errno); + if (rc < 0) { + EXPECT_NE(ECAPMODE, errno); + } #endif char buf[1024]; rc = getdents_(fd_dir_, (void*)buf, sizeof(buf)); EXPECT_OK(rc); char data[] = "123"; EXPECT_OK(pwrite(fd_file_, data, 1, 0)); EXPECT_OK(pread(fd_file_, data, 1, 0)); struct iovec io; io.iov_base = data; io.iov_len = 2; #if !defined(__i386__) && !defined(__linux__) // TODO(drysdale): reinstate these tests for 32-bit runs when possible // libc bug is fixed. EXPECT_OK(pwritev(fd_file_, &io, 1, 0)); EXPECT_OK(preadv(fd_file_, &io, 1, 0)); #endif EXPECT_OK(writev(fd_file_, &io, 1)); EXPECT_OK(readv(fd_file_, &io, 1)); #ifdef HAVE_SYNCFS EXPECT_OK(syncfs(fd_file_)); #endif #ifdef HAVE_SYNC_FILE_RANGE EXPECT_OK(sync_file_range(fd_file_, 0, 1, 0)); #endif #ifdef HAVE_READAHEAD if (!tmpdir_on_tmpfs) { // tmpfs doesn't support readahead(2) EXPECT_OK(readahead(fd_file_, 0, 1)); } #endif } FORK_TEST_F(WithFiles, AllowedSocketSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. // recvfrom() either returns -1 with EAGAIN, or 0. int rc = recvfrom(fd_socket_, NULL, 0, MSG_DONTWAIT, NULL, NULL); - if (rc < 0) EXPECT_EQ(EAGAIN, errno); + if (rc < 0) { + EXPECT_EQ(EAGAIN, errno); + } char ch; EXPECT_OK(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. EXPECT_FAIL_NOT_CAPMODE(accept(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(getpeername(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(getsockname(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(recvmsg(fd_socket_, NULL, 0)); EXPECT_FAIL_NOT_CAPMODE(sendmsg(fd_socket_, NULL, 0)); EXPECT_FAIL_NOT_CAPMODE(sendto(fd_socket_, NULL, 0, 0, NULL, 0)); off_t offset = 0; EXPECT_FAIL_NOT_CAPMODE(sendfile_(fd_socket_, fd_file_, &offset, 1)); // The socket/socketpair syscalls are allowed, but they don't give // anything externally useful (can't call bind/connect on them). int fd_socket2 = socket(PF_INET, SOCK_DGRAM, 0); EXPECT_OK(fd_socket2); if (fd_socket2 >= 0) close(fd_socket2); int fd_pair[2] = {-1, -1}; EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, fd_pair)); if (fd_pair[0] >= 0) close(fd_pair[0]); if (fd_pair[1] >= 0) close(fd_pair[1]); } #ifdef HAVE_SEND_RECV_MMSG FORK_TEST(Capmode, AllowedMmsgSendRecv) { int fd_socket = socket(PF_INET, SOCK_DGRAM, 0); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(0); addr.sin_addr.s_addr = htonl(INADDR_ANY); EXPECT_OK(bind(fd_socket, (sockaddr*)&addr, sizeof(addr))); EXPECT_OK(cap_enter()); // Enter capability mode. char buffer[256] = {0}; struct iovec iov; iov.iov_base = buffer; iov.iov_len = sizeof(buffer); struct mmsghdr mm; memset(&mm, 0, sizeof(mm)); mm.msg_hdr.msg_iov = &iov; mm.msg_hdr.msg_iovlen = 1; struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 100; EXPECT_FAIL_NOT_CAPMODE(recvmmsg(fd_socket, &mm, 1, MSG_DONTWAIT, &ts)); EXPECT_FAIL_NOT_CAPMODE(sendmmsg(fd_socket, &mm, 1, 0)); close(fd_socket); } #endif FORK_TEST(Capmode, AllowedIdentifierSyscalls) { // Record some identifiers gid_t my_gid = getgid(); pid_t my_pid = getpid(); pid_t my_ppid = getppid(); uid_t my_uid = getuid(); pid_t my_sid = getsid(my_pid); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_EQ(my_gid, getegid_()); EXPECT_EQ(my_uid, geteuid_()); EXPECT_EQ(my_gid, getgid_()); EXPECT_EQ(my_pid, getpid()); EXPECT_EQ(my_ppid, getppid()); EXPECT_EQ(my_uid, getuid_()); EXPECT_EQ(my_sid, getsid(my_pid)); gid_t grps[128]; EXPECT_OK(getgroups_(128, grps)); uid_t ruid; uid_t euid; uid_t suid; EXPECT_OK(getresuid(&ruid, &euid, &suid)); gid_t rgid; gid_t egid; gid_t sgid; EXPECT_OK(getresgid(&rgid, &egid, &sgid)); #ifdef HAVE_GETLOGIN EXPECT_TRUE(getlogin() != NULL); #endif // Set various identifiers (to their existing values). EXPECT_OK(setgid(my_gid)); #ifdef HAVE_SETFSGID EXPECT_OK(setfsgid(my_gid)); #endif EXPECT_OK(setuid(my_uid)); #ifdef HAVE_SETFSUID EXPECT_OK(setfsuid(my_uid)); #endif EXPECT_OK(setregid(my_gid, my_gid)); EXPECT_OK(setresgid(my_gid, my_gid, my_gid)); EXPECT_OK(setreuid(my_uid, my_uid)); EXPECT_OK(setresuid(my_uid, my_uid, my_uid)); EXPECT_OK(setsid()); } FORK_TEST(Capmode, AllowedSchedSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. int policy = sched_getscheduler(0); EXPECT_OK(policy); struct sched_param sp; EXPECT_OK(sched_getparam(0, &sp)); if (policy >= 0 && (!SCHED_SETSCHEDULER_REQUIRES_ROOT || getuid() == 0)) { EXPECT_OK(sched_setscheduler(0, policy, &sp)); } EXPECT_OK(sched_setparam(0, &sp)); EXPECT_OK(sched_get_priority_max(policy)); EXPECT_OK(sched_get_priority_min(policy)); struct timespec ts; EXPECT_OK(sched_rr_get_interval(0, &ts)); EXPECT_OK(sched_yield()); } FORK_TEST(Capmode, AllowedTimerSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. struct timespec ts; EXPECT_OK(clock_getres(CLOCK_REALTIME, &ts)); EXPECT_OK(clock_gettime(CLOCK_REALTIME, &ts)); struct itimerval itv; EXPECT_OK(getitimer(ITIMER_REAL, &itv)); EXPECT_OK(setitimer(ITIMER_REAL, &itv, NULL)); struct timeval tv; struct timezone tz; EXPECT_OK(gettimeofday(&tv, &tz)); ts.tv_sec = 0; ts.tv_nsec = 1; EXPECT_OK(nanosleep(&ts, NULL)); } FORK_TEST(Capmode, AllowedProfilSyscall) { EXPECT_OK(cap_enter()); // Enter capability mode. char sbuf[32]; EXPECT_OK(profil((profil_arg1_t*)sbuf, sizeof(sbuf), 0, 1)); } FORK_TEST(Capmode, AllowedResourceSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. errno = 0; int rc = getpriority(PRIO_PROCESS, 0); EXPECT_EQ(0, errno); EXPECT_OK(setpriority(PRIO_PROCESS, 0, rc)); struct rlimit rlim; EXPECT_OK(getrlimit_(RLIMIT_CORE, &rlim)); EXPECT_OK(setrlimit(RLIMIT_CORE, &rlim)); struct rusage ruse; EXPECT_OK(getrusage(RUSAGE_SELF, &ruse)); } FORK_TEST(CapMode, AllowedMmapSyscalls) { // mmap() some memory. size_t mem_size = getpagesize(); void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); EXPECT_TRUE(mem != NULL); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(msync(mem, mem_size, MS_ASYNC)); EXPECT_OK(madvise(mem, mem_size, MADV_NORMAL)); unsigned char vec[2]; EXPECT_OK(mincore_(mem, mem_size, vec)); EXPECT_OK(mprotect(mem, mem_size, PROT_READ|PROT_WRITE)); if (!MLOCK_REQUIRES_ROOT || getuid() == 0) { EXPECT_OK(mlock(mem, mem_size)); EXPECT_OK(munlock(mem, mem_size)); int rc = mlockall(MCL_CURRENT); if (rc != 0) { // mlockall may well fail with ENOMEM for non-root users, as the // default RLIMIT_MEMLOCK value isn't that big. EXPECT_NE(ECAPMODE, errno); } EXPECT_OK(munlockall()); } // Unmap the memory. EXPECT_OK(munmap(mem, mem_size)); } FORK_TEST(Capmode, AllowedPipeSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode int fd2[2]; int rc = pipe(fd2); EXPECT_EQ(0, rc); #ifdef HAVE_VMSPLICE char buf[11] = "0123456789"; struct iovec iov; iov.iov_base = buf; iov.iov_len = sizeof(buf); EXPECT_FAIL_NOT_CAPMODE(vmsplice(fd2[0], &iov, 1, SPLICE_F_NONBLOCK)); #endif if (rc == 0) { close(fd2[0]); close(fd2[1]); }; #ifdef HAVE_PIPE2 rc = pipe2(fd2, 0); EXPECT_EQ(0, rc); if (rc == 0) { close(fd2[0]); close(fd2[1]); }; #endif } TEST(Capmode, AllowedAtSyscalls) { int rc = mkdir(TmpFile("cap_at_syscalls"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; int dfd = open(TmpFile("cap_at_syscalls"), O_RDONLY); EXPECT_OK(dfd); int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644); EXPECT_OK(file); EXPECT_OK(close(file)); pid_t child = fork(); if (child == 0) { // Child: enter cap mode and run tests EXPECT_OK(cap_enter()); // Enter capability mode struct stat fs; EXPECT_OK(fstatat(dfd, "testfile", &fs, 0)); EXPECT_OK(mkdirat(dfd, "subdir", 0600)); EXPECT_OK(fchmodat(dfd, "subdir", 0644, 0)); EXPECT_OK(faccessat(dfd, "subdir", F_OK, 0)); EXPECT_OK(renameat(dfd, "subdir", dfd, "subdir2")); EXPECT_OK(renameat(dfd, "subdir2", dfd, "subdir")); struct timeval tv[2]; struct timezone tz; EXPECT_OK(gettimeofday(&tv[0], &tz)); EXPECT_OK(gettimeofday(&tv[1], &tz)); EXPECT_OK(futimesat(dfd, "testfile", tv)); EXPECT_OK(fchownat(dfd, "testfile", fs.st_uid, fs.st_gid, 0)); EXPECT_OK(linkat(dfd, "testfile", dfd, "linky", 0)); EXPECT_OK(symlinkat("testfile", dfd, "symlink")); char buffer[256]; EXPECT_OK(readlinkat(dfd, "symlink", buffer, sizeof(buffer))); EXPECT_OK(unlinkat(dfd, "linky", 0)); EXPECT_OK(unlinkat(dfd, "subdir", AT_REMOVEDIR)); // Check that invalid requests get a non-Capsicum errno. errno = 0; rc = readlinkat(-1, "symlink", buffer, sizeof(buffer)); EXPECT_GE(0, rc); EXPECT_NE(ECAPMODE, errno); exit(HasFailure()); } // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); // Tidy up. close(dfd); rmdir(TmpFile("cap_at_syscalls/subdir")); unlink(TmpFile("cap_at_syscalls/symlink")); unlink(TmpFile("cap_at_syscalls/linky")); unlink(TmpFile("cap_at_syscalls/testfile")); rmdir(TmpFile("cap_at_syscalls")); } TEST(Capmode, AllowedAtSyscallsCwd) { int rc = mkdir(TmpFile("cap_at_syscalls_cwd"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; int dfd = open(TmpFile("cap_at_syscalls_cwd"), O_RDONLY); EXPECT_OK(dfd); int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644); EXPECT_OK(file); EXPECT_OK(close(file)); pid_t child = fork(); if (child == 0) { // Child: move into temp dir, enter cap mode and run tests EXPECT_OK(fchdir(dfd)); EXPECT_OK(cap_enter()); // Enter capability mode // Test that *at(AT_FDCWD, path,...) is policed with ECAPMODE. EXPECT_CAPMODE(openat(AT_FDCWD, "testfile", O_RDONLY)); struct stat fs; EXPECT_CAPMODE(fstatat(AT_FDCWD, "testfile", &fs, 0)); EXPECT_CAPMODE(mkdirat(AT_FDCWD, "subdir", 0600)); EXPECT_CAPMODE(fchmodat(AT_FDCWD, "subdir", 0644, 0)); EXPECT_CAPMODE(faccessat(AT_FDCWD, "subdir", F_OK, 0)); EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir", AT_FDCWD, "subdir2")); EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir2", AT_FDCWD, "subdir")); struct timeval tv[2]; struct timezone tz; EXPECT_OK(gettimeofday(&tv[0], &tz)); EXPECT_OK(gettimeofday(&tv[1], &tz)); EXPECT_CAPMODE(futimesat(AT_FDCWD, "testfile", tv)); EXPECT_CAPMODE(fchownat(AT_FDCWD, "testfile", fs.st_uid, fs.st_gid, 0)); EXPECT_CAPMODE(linkat(AT_FDCWD, "testfile", AT_FDCWD, "linky", 0)); EXPECT_CAPMODE(symlinkat("testfile", AT_FDCWD, "symlink")); char buffer[256]; EXPECT_CAPMODE(readlinkat(AT_FDCWD, "symlink", buffer, sizeof(buffer))); EXPECT_CAPMODE(unlinkat(AT_FDCWD, "linky", 0)); exit(HasFailure()); } // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); // Tidy up. close(dfd); rmdir(TmpFile("cap_at_syscalls_cwd/subdir")); unlink(TmpFile("cap_at_syscalls_cwd/symlink")); unlink(TmpFile("cap_at_syscalls_cwd/linky")); unlink(TmpFile("cap_at_syscalls_cwd/testfile")); rmdir(TmpFile("cap_at_syscalls_cwd")); } TEST(Capmode, Abort) { // Check that abort(3) works even in capability mode. pid_t child = fork(); if (child == 0) { // Child: enter capability mode and call abort(3). // Triggers something like kill(getpid(), SIGABRT). cap_enter(); // Enter capability mode. abort(); exit(99); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFSIGNALED(status)) << " status = " << std::hex << status; EXPECT_EQ(SIGABRT, WTERMSIG(status)) << " status = " << std::hex << status; } FORK_TEST_F(WithFiles, AllowedMiscSyscalls) { umask(022); mode_t um_before = umask(022); EXPECT_OK(cap_enter()); // Enter capability mode. mode_t um = umask(022); EXPECT_NE(-ECAPMODE, (int)um); EXPECT_EQ(um_before, um); stack_t ss; EXPECT_OK(sigaltstack(NULL, &ss)); // Finally, tests for system calls that don't fit the pattern very well. pid_t pid = fork(); EXPECT_OK(pid); if (pid == 0) { // Child: almost immediately exit. sleep(1); exit(0); } else if (pid > 0) { errno = 0; EXPECT_CAPMODE(ptrace_(PTRACE_PEEKDATA_, pid, &pid, NULL)); EXPECT_CAPMODE(waitpid(pid, NULL, 0)); } // No error return from sync(2) to test, but check errno remains unset. errno = 0; sync(); EXPECT_EQ(0, errno); // TODO(FreeBSD): ktrace #ifdef HAVE_SYSARCH // sysarch() is, by definition, architecture-dependent #if defined (__amd64__) || defined (__i386__) long sysarch_arg = 0; EXPECT_CAPMODE(sysarch(I386_SET_IOPERM, &sysarch_arg)); #else // TOOD(jra): write a test for other architectures, like arm #endif #endif } void *thread_fn(void *p) { int delay = *(int *)p; sleep(delay); EXPECT_OK(getpid_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); return NULL; } // Check that restrictions are the same in subprocesses and threads FORK_TEST(Capmode, NewThread) { // Fire off a new thread before entering capability mode pthread_t early_thread; int one = 1; // second EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, &one)); // Fire off a new process before entering capability mode. int early_child = fork(); EXPECT_OK(early_child); if (early_child == 0) { // Child: wait and then confirm this process is unaffect by capability mode in the parent. sleep(1); int fd = open("/dev/null", O_RDWR); EXPECT_OK(fd); close(fd); exit(0); } EXPECT_OK(cap_enter()); // Enter capability mode. // Do an allowed syscall. EXPECT_OK(getpid_()); int child = fork(); EXPECT_OK(child); if (child == 0) { // Child: do an allowed and a disallowed syscall. EXPECT_OK(getpid_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); exit(0); } // Don't (can't) wait for either child. // Wait for the early-started thread. EXPECT_OK(pthread_join(early_thread, NULL)); // Fire off a new thread. pthread_t child_thread; int zero = 0; // seconds EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, &zero)); EXPECT_OK(pthread_join(child_thread, NULL)); // Fork a subprocess which fires off a new thread. child = fork(); EXPECT_OK(child); if (child == 0) { pthread_t child_thread2; EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, &zero)); EXPECT_OK(pthread_join(child_thread2, NULL)); exit(0); } // Sleep for a bit to allow the subprocess to finish. sleep(2); } static int had_signal = 0; static void handle_signal(int) { had_signal = 1; } FORK_TEST(Capmode, SelfKill) { pid_t me = getpid(); sighandler_t original = signal(SIGUSR1, handle_signal); pid_t child = fork(); if (child == 0) { // Child: sleep and exit sleep(1); exit(0); } EXPECT_OK(cap_enter()); // Enter capability mode. // Can only kill(2) to own pid. EXPECT_CAPMODE(kill(child, SIGUSR1)); EXPECT_OK(kill(me, SIGUSR1)); EXPECT_EQ(1, had_signal); signal(SIGUSR1, original); } Index: projects/capsicum-test/contrib/capsicum-test/capsicum-test.h =================================================================== --- projects/capsicum-test/contrib/capsicum-test/capsicum-test.h (revision 345423) +++ projects/capsicum-test/contrib/capsicum-test/capsicum-test.h (revision 345424) @@ -1,263 +1,260 @@ /* -*- C++ -*- */ #ifndef CAPSICUM_TEST_H #define CAPSICUM_TEST_H #include #include #include #include #include #include #include #include "gtest/gtest.h" extern bool verbose; extern std::string tmpdir; extern bool tmpdir_on_tmpfs; extern bool force_mt; extern bool force_nofork; extern uid_t other_uid; static inline void *WaitingThreadFn(void *) { // Loop until cancelled while (true) { usleep(10000); pthread_testcancel(); } return NULL; } // If force_mt is set, run another thread in parallel with the test. This forces // the kernel into multi-threaded mode. template void MaybeRunWithThread(T *self, Function fn) { pthread_t subthread; if (force_mt) { pthread_create(&subthread, NULL, WaitingThreadFn, NULL); } (self->*fn)(); if (force_mt) { pthread_cancel(subthread); pthread_join(subthread, NULL); } } template void MaybeRunWithThread(Function fn) { pthread_t subthread; if (force_mt) { pthread_create(&subthread, NULL, WaitingThreadFn, NULL); } (fn)(); if (force_mt) { pthread_cancel(subthread); pthread_join(subthread, NULL); } } // Return the absolute path of a filename in the temp directory, `tmpdir`, // with the given pathname, e.g., "/tmp/", if `tmpdir` was set to // "/tmp". const char *TmpFile(const char *pathname); // Run the given test function in a forked process, so that trapdoor // entry doesn't affect other tests, and watch out for hung processes. // Implemented as a macro to allow access to the test case instance's // HasFailure() method, which is reported as the forked process's // exit status. #define _RUN_FORKED(INNERCODE, TESTCASENAME, TESTNAME) \ pid_t pid = force_nofork ? 0 : fork(); \ if (pid == 0) { \ INNERCODE; \ if (!force_nofork) { \ exit(HasFailure()); \ } \ } else if (pid > 0) { \ int rc, status; \ int remaining_us = 10000000; \ while (remaining_us > 0) { \ status = 0; \ rc = waitpid(pid, &status, WNOHANG); \ if (rc != 0) break; \ remaining_us -= 10000; \ usleep(10000); \ } \ if (remaining_us <= 0) { \ fprintf(stderr, "Warning: killing unresponsive test " \ "%s.%s (pid %d)\n", \ TESTCASENAME, TESTNAME, pid); \ kill(pid, SIGKILL); \ ADD_FAILURE() << "Test hung"; \ } else if (rc < 0) { \ fprintf(stderr, "Warning: waitpid error %s (%d)\n", \ strerror(errno), errno); \ ADD_FAILURE() << "Failed to wait for child"; \ } else { \ int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; \ EXPECT_EQ(0, rc); \ } \ } #define _RUN_FORKED_MEM(THIS, TESTFN, TESTCASENAME, TESTNAME) \ _RUN_FORKED(MaybeRunWithThread(THIS, &TESTFN), TESTCASENAME, TESTNAME); #define _RUN_FORKED_FN(TESTFN, TESTCASENAME, TESTNAME) \ _RUN_FORKED(MaybeRunWithThread(&TESTFN), TESTCASENAME, TESTNAME); // Run a test case in a forked process, possibly cleaning up a // test file after completion #define FORK_TEST_ON(test_case_name, test_name, test_file) \ static void test_case_name##_##test_name##_ForkTest(); \ TEST(test_case_name, test_name ## Forked) { \ _RUN_FORKED_FN(test_case_name##_##test_name##_ForkTest, \ #test_case_name, #test_name); \ const char *filename = test_file; \ if (filename) unlink(filename); \ } \ static void test_case_name##_##test_name##_ForkTest() #define FORK_TEST(test_case_name, test_name) FORK_TEST_ON(test_case_name, test_name, NULL) // Run a test case fixture in a forked process, so that trapdoors don't // affect other tests. #define ICLASS_NAME(test_case_name, test_name) Forked##test_case_name##_##test_name #define FORK_TEST_F(test_case_name, test_name) \ class ICLASS_NAME(test_case_name, test_name) : public test_case_name { \ public: \ ICLASS_NAME(test_case_name, test_name)() {} \ void InnerTestBody(); \ }; \ TEST_F(ICLASS_NAME(test_case_name, test_name), _) { \ _RUN_FORKED_MEM(this, \ ICLASS_NAME(test_case_name, test_name)::InnerTestBody, \ #test_case_name, #test_name); \ } \ void ICLASS_NAME(test_case_name, test_name)::InnerTestBody() // Emit errno information on failure -#define EXPECT_OK(v) \ - do { \ - EXPECT_LE(0, v) << " errno " << errno << " " << strerror(errno); \ - } while (0) +#define EXPECT_OK(v) EXPECT_LE(0, v) << " errno " << errno << " " << strerror(errno) // Expect a syscall to fail with the given error. #define EXPECT_SYSCALL_FAIL(E, C) \ do { \ EXPECT_GT(0, C); \ EXPECT_EQ(E, errno); \ } while (0) // Expect a syscall to fail with anything other than the given error. #define EXPECT_SYSCALL_FAIL_NOT(E, C) \ do { \ EXPECT_GT(0, C); \ EXPECT_NE(E, errno); \ } while (0) // Expect a void syscall to fail with anything other than the given error. #define EXPECT_VOID_SYSCALL_FAIL_NOT(E, C) \ do { \ errno = 0; \ C; \ EXPECT_NE(E, errno) << #C << " failed with ECAPMODE"; \ } while (0) // Expect a system call to fail due to path traversal; exact error // code is OS-specific. #ifdef O_BENEATH #define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \ do { \ const int result = openat((fd), (path), (flags)); \ if (((flags) & O_BENEATH) == O_BENEATH) { \ EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_O_BENEATH, result); \ } else { \ EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \ } \ } while (0) #else #define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \ do { \ const int result = openat((fd), (path), (flags)); \ EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \ } while (0) #endif // Expect a system call to fail with ECAPMODE. #define EXPECT_CAPMODE(C) EXPECT_SYSCALL_FAIL(ECAPMODE, C) // Expect a system call to fail, but not with ECAPMODE. #define EXPECT_FAIL_NOT_CAPMODE(C) EXPECT_SYSCALL_FAIL_NOT(ECAPMODE, C) #define EXPECT_FAIL_VOID_NOT_CAPMODE(C) EXPECT_VOID_SYSCALL_FAIL_NOT(ECAPMODE, C) // Expect a system call to fail with ENOTCAPABLE. #define EXPECT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL(ENOTCAPABLE, C) // Expect a system call to fail, but not with ENOTCAPABLE. #define EXPECT_FAIL_NOT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL_NOT(ENOTCAPABLE, C) // Expect a system call to fail with either ENOTCAPABLE or ECAPMODE. #define EXPECT_CAPFAIL(C) \ do { \ int rc = C; \ EXPECT_GT(0, rc); \ EXPECT_TRUE(errno == ECAPMODE || errno == ENOTCAPABLE) \ << #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno; \ } while (0) // Ensure that 'rights' are a subset of 'max'. #define EXPECT_RIGHTS_IN(rights, max) \ EXPECT_TRUE(cap_rights_contains((max), (rights))) \ << "rights " << std::hex << *(rights) \ << " not a subset of " << std::hex << *(max) // Ensure rights are identical #define EXPECT_RIGHTS_EQ(a, b) \ do { \ EXPECT_RIGHTS_IN((a), (b)); \ EXPECT_RIGHTS_IN((b), (a)); \ } while (0) // Get the state of a process as a single character. // - 'D': disk wait // - 'R': runnable // - 'S': sleeping/idle // - 'T': stopped // - 'Z': zombie // On error, return either '?' or '\0'. char ProcessState(int pid); // Check process state reaches a particular expected state (or two). // Retries a few times to allow for timing issues. #define EXPECT_PID_REACHES_STATES(pid, expected1, expected2) { \ int counter = 5; \ char state; \ do { \ state = ProcessState(pid); \ if (state == expected1 || state == expected2) break; \ usleep(100000); \ } while (--counter > 0); \ EXPECT_TRUE(state == expected1 || state == expected2) \ << " pid " << pid << " in state " << state; \ } #define EXPECT_PID_ALIVE(pid) EXPECT_PID_REACHES_STATES(pid, 'R', 'S') #define EXPECT_PID_DEAD(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', '\0') #define EXPECT_PID_ZOMBIE(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', 'Z'); #define EXPECT_PID_GONE(pid) EXPECT_PID_REACHES_STATES(pid, '\0', '\0'); void ShowSkippedTests(std::ostream& os); void TestSkipped(const char *testcase, const char *test, const std::string& reason); #define TEST_SKIPPED(reason) \ do { \ const ::testing::TestInfo* const info = ::testing::UnitTest::GetInstance()->current_test_info(); \ std::cerr << "Skipping " << info->test_case_name() << "::" << info->name() << " because: " << reason << std::endl; \ TestSkipped(info->test_case_name(), info->name(), reason); \ } while (0) // Mark a test that can only be run as root. #define REQUIRE_ROOT() \ if (getuid() != 0) { \ TEST_SKIPPED("requires root"); \ return; \ } #endif // CAPSICUM_TEST_H Index: projects/capsicum-test/contrib/capsicum-test/openat.cc =================================================================== --- projects/capsicum-test/contrib/capsicum-test/openat.cc (revision 345423) +++ projects/capsicum-test/contrib/capsicum-test/openat.cc (revision 345424) @@ -1,357 +1,361 @@ #include #include #include #include #include #include "capsicum.h" #include "capsicum-test.h" #include "syscalls.h" // Check an open call works and close the resulting fd. #define EXPECT_OPEN_OK(f) do { \ int _fd = f; \ EXPECT_OK(_fd); \ close(_fd); \ } while (0) static void CreateFile(const char *filename, const char *contents) { int fd = open(filename, O_CREAT|O_RDWR, 0644); EXPECT_OK(fd); EXPECT_OK(write(fd, contents, strlen(contents))); close(fd); } // 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. // // These rules apply when: // - the directory FD is a Capsicum capability // - the process is in capability mode // - the openat(2) operation includes the O_BENEATH flag. FORK_TEST(Openat, Relative) { int etc = open("/etc/", O_RDONLY); EXPECT_OK(etc); cap_rights_t r_base; cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL); cap_rights_t r_ro; cap_rights_init(&r_ro, CAP_READ); cap_rights_t r_rl; cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP); int etc_cap = dup(etc); EXPECT_OK(etc_cap); EXPECT_OK(cap_rights_limit(etc_cap, &r_ro)); int etc_cap_ro = dup(etc); EXPECT_OK(etc_cap_ro); EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl)); int etc_cap_base = dup(etc); EXPECT_OK(etc_cap_base); EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base)); #ifdef HAVE_CAP_FCNTLS_LIMIT // Also limit fcntl(2) subrights. EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL)); #endif #ifdef HAVE_CAP_IOCTLS_LIMIT // Also limit ioctl(2) subrights. cap_ioctl_t ioctl_nread = FIONREAD; EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1)); #endif // openat(2) with regular file descriptors in non-capability mode // Should Just Work (tm). EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); EXPECT_OPEN_OK(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. EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); // Performing openat(2) on a path with leading slash ignores // the provided directory FD. EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY)); // Relative lookups that go upward are not allowed. EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); // A file opened relative to a capability should itself be a capability. int fd = openat(etc_cap_base, "passwd", O_RDONLY); EXPECT_OK(fd); cap_rights_t rights; EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_base); #ifdef HAVE_CAP_FCNTLS_LIMIT cap_fcntl_t fcntls; EXPECT_OK(cap_fcntls_get(fd, &fcntls)); EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls); #endif #ifdef HAVE_CAP_IOCTLS_LIMIT cap_ioctl_t ioctls[16]; ssize_t nioctls; memset(ioctls, 0, sizeof(ioctls)); nioctls = cap_ioctls_get(fd, ioctls, 16); EXPECT_OK(nioctls); EXPECT_EQ(1, nioctls); EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]); #endif close(fd); // Enter capability mode; now ALL lookups are strictly relative. EXPECT_OK(cap_enter()); // Relative lookups on regular files or capabilities with CAP_LOOKUP // ought to succeed. EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); // Lookup relative to capabilities without CAP_LOOKUP should fail. EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY)); // Absolute lookups should fail. EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY); // Lookups containing '..' should fail in capability mode. EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); fd = openat(etc, "passwd", O_RDONLY); EXPECT_OK(fd); // A file opened relative to a capability should itself be a capability. fd = openat(etc_cap_base, "passwd", O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_base); close(fd); fd = openat(etc_cap_ro, "passwd", O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_rl); close(fd); } #define TOPDIR "cap_topdir" #define SUBDIR TOPDIR "/subdir" class OpenatTest : public ::testing::Test { public: // Build a collection of files, subdirs and symlinks: // /tmp/cap_topdir/ // /topfile // /subdir/ // /subdir/bottomfile // /symlink.samedir -> topfile // /dsymlink.samedir -> ./ // /symlink.down -> subdir/bottomfile // /dsymlink.down -> subdir/ // /symlink.absolute_out -> /etc/passwd // /dsymlink.absolute_out -> /etc/ // /symlink.relative_in -> ../../tmp/cap_topdir/topfile // /dsymlink.relative_in -> ../../tmp/cap_topdir/ // /symlink.relative_out -> ../../etc/passwd // /dsymlink.relative_out -> ../../etc/ // /subdir/dsymlink.absolute_in -> /tmp/cap_topdir/ // /subdir/dsymlink.up -> ../ // /subdir/symlink.absolute_in -> /tmp/cap_topdir/topfile // /subdir/symlink.up -> ../topfile // (In practice, this is a little more complicated because tmpdir might // not be "/tmp".) OpenatTest() { // Create a couple of nested directories int rc = mkdir(TmpFile(TOPDIR), 0755); EXPECT_OK(rc); - if (rc < 0) EXPECT_EQ(EEXIST, errno); + if (rc < 0) { + EXPECT_EQ(EEXIST, errno); + } rc = mkdir(TmpFile(SUBDIR), 0755); EXPECT_OK(rc); - if (rc < 0) EXPECT_EQ(EEXIST, errno); + if (rc < 0) { + EXPECT_EQ(EEXIST, errno); + } // Figure out a path prefix (like "../..") that gets us to the root // directory from TmpFile(TOPDIR). const char *p = TmpFile(TOPDIR); // maybe "/tmp/somewhere/cap_topdir" std::string dots2root = ".."; while (*p++ != '\0') { if (*p == '/') { dots2root += "/.."; } } // Create normal files in each. CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file"); CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory"); // Create various symlinks to files. EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir"))); EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down"))); EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(SUBDIR "/symlink.absolute_in"))); EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out"))); std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile"); EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in"))); std::string dots2passwd = dots2root + "/etc/passwd"; EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out"))); EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR "/symlink.up"))); // Create various symlinks to directories. EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir"))); EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down"))); EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(SUBDIR "/dsymlink.absolute_in"))); EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out"))); std::string dots2cwd = dots2root + tmpdir + "/"; EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in"))); std::string dots2etc = dots2root + "/etc/"; EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out"))); EXPECT_OK(symlink("../", TmpFile(SUBDIR "/dsymlink.up"))); // Open directory FDs for those directories and for cwd. dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY); EXPECT_OK(dir_fd_); sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY); EXPECT_OK(sub_fd_); cwd_ = openat(AT_FDCWD, ".", O_RDONLY); EXPECT_OK(cwd_); // Move into the directory for the test. EXPECT_OK(fchdir(dir_fd_)); } ~OpenatTest() { fchdir(cwd_); close(cwd_); close(sub_fd_); close(dir_fd_); unlink(TmpFile(SUBDIR "/symlink.up")); unlink(TmpFile(SUBDIR "/symlink.absolute_in")); unlink(TmpFile(TOPDIR "/symlink.absolute_out")); unlink(TmpFile(TOPDIR "/symlink.relative_in")); unlink(TmpFile(TOPDIR "/symlink.relative_out")); unlink(TmpFile(TOPDIR "/symlink.down")); unlink(TmpFile(TOPDIR "/symlink.samedir")); unlink(TmpFile(SUBDIR "/dsymlink.up")); unlink(TmpFile(SUBDIR "/dsymlink.absolute_in")); unlink(TmpFile(TOPDIR "/dsymlink.absolute_out")); unlink(TmpFile(TOPDIR "/dsymlink.relative_in")); unlink(TmpFile(TOPDIR "/dsymlink.relative_out")); unlink(TmpFile(TOPDIR "/dsymlink.down")); unlink(TmpFile(TOPDIR "/dsymlink.samedir")); unlink(TmpFile(SUBDIR "/bottomfile")); unlink(TmpFile(TOPDIR "/topfile")); rmdir(TmpFile(SUBDIR)); rmdir(TmpFile(TOPDIR)); } // Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH. void CheckPolicing(int oflag) { // OK for normal access. EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag)); // Can't open paths with ".." in them. EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag); #ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT // OK for dotdot lookups that don't escape the top directory EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag)); #endif // Check that we can't escape the top directory by the cunning // ruse of going via a subdirectory. EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag); // Should only be able to open symlinks that stay within the directory. EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag)); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.absolute_in", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag); EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag)); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag); // Although recall that O_NOFOLLOW prevents symlink following in final component. EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag)); EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag)); } protected: int dir_fd_; int sub_fd_; int cwd_; }; TEST_F(OpenatTest, WithCapability) { // Any kind of symlink can be opened relative to an ordinary directory FD. EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY)); EXPECT_OPEN_OK(openat(sub_fd_, "symlink.absolute_in", O_RDONLY)); EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY)); // Now make both DFDs into Capsicum capabilities. cap_rights_t r_rl; cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR); EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl)); EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl)); CheckPolicing(0); // Use of AT_FDCWD is independent of use of a capability. // Can open paths starting with "/" against a capability dfd, because the dfd is ignored. } FORK_TEST_F(OpenatTest, InCapabilityMode) { EXPECT_OK(cap_enter()); // Enter capability mode CheckPolicing(0); // Use of AT_FDCWD is banned in capability mode. EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY)); EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY)); EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); // Can't open paths starting with "/" in capability mode. EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY); } #ifdef O_BENEATH TEST_F(OpenatTest, WithFlag) { CheckPolicing(O_BENEATH); // Check with AT_FDCWD. EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH)); EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_BENEATH)); // Can't open paths starting with "/" with O_BENEATH specified. EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_BENEATH); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); } FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) { EXPECT_OK(cap_enter()); // Enter capability mode CheckPolicing(O_BENEATH); } #endif