diff --git a/contrib/capsicum-test/capability-fd.cc b/contrib/capsicum-test/capability-fd.cc index 6c470cff3418..a454d54aa86a 100644 --- a/contrib/capsicum-test/capability-fd.cc +++ b/contrib/capsicum-test/capability-fd.cc @@ -1,1309 +1,1335 @@ #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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } 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)); } #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)); } #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)); } } // 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)); } 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")) { +TEST(Capability, SyscallAtIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); + 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_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); + + 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_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)); + + // 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")); + + close(cap_dfd_all); + close(cap_dfd_no_mknod); + close(cap_dfd_no_mkfifo); + close(dfd); + + // Tidy up. + rmdir(TmpFile("cap_at_topdir")); +} + +FORK_TEST_ON(Capability, ExtendedAttributesIfAvailable, 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; + GTEST_SKIP() << "/tmp doesn't support extended attributes"; } 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(); +TEST(Capability, NoBypassDACIfRoot) { + GTEST_SKIP_IF_NOT_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); + ASSERT_NE(0u, other_uid) << "other_uid not initialized correctly, " + "please pass the -u flag."; + EXPECT_EQ(0, setuid(other_uid)); + EXPECT_EQ(other_uid, getuid()); // 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")); } diff --git a/contrib/capsicum-test/capsicum-test-main.cc b/contrib/capsicum-test/capsicum-test-main.cc index 524631ebf5bd..d0f955270fd4 100644 --- a/contrib/capsicum-test/capsicum-test-main.cc +++ b/contrib/capsicum-test/capsicum-test-main.cc @@ -1,156 +1,160 @@ #include #ifdef __linux__ #include #include #elif defined(__FreeBSD__) #include #endif #include #include #include #include #include #include #include #include #include "gtest/gtest.h" #include "capsicum-test.h" // For versions of googletest that lack GTEST_SKIP. #ifndef GTEST_SKIP #define GTEST_SKIP GTEST_FAIL #endif std::string tmpdir; class SetupEnvironment : public ::testing::Environment { public: SetupEnvironment() : teardown_tmpdir_(false) {} void SetUp() override { CheckCapsicumSupport(); if (tmpdir.empty()) { std::cerr << "Generating temporary directory root: "; CreateTemporaryRoot(); } else { std::cerr << "User provided temporary directory root: "; } std::cerr << tmpdir << std::endl; } void CheckCapsicumSupport() { #ifdef __FreeBSD__ int rc; bool trap_enotcap_enabled; size_t trap_enotcap_enabled_len = sizeof(trap_enotcap_enabled); if (feature_present("security_capabilities") == 0) { GTEST_SKIP() << "Skipping tests because capsicum support is not " << "enabled in the kernel."; } // If this OID is enabled, it will send SIGTRAP to the process when // `ENOTCAPABLE` is returned. const char *oid = "kern.trap_enotcap"; rc = sysctlbyname(oid, &trap_enotcap_enabled, &trap_enotcap_enabled_len, nullptr, 0); if (rc != 0) { GTEST_FAIL() << "sysctlbyname failed: " << strerror(errno); } if (trap_enotcap_enabled) { GTEST_SKIP() << "Debug sysctl, " << oid << ", enabled. " << "Skipping tests because its enablement invalidates the " << "test results."; } #endif /* FreeBSD */ } void CreateTemporaryRoot() { char *tmpdir_name = tempnam(nullptr, "cptst"); ASSERT_NE(tmpdir_name, nullptr); ASSERT_EQ(mkdir(tmpdir_name, 0700), 0) << "Could not create temp directory, " << tmpdir_name << ": " << strerror(errno); tmpdir = std::string(tmpdir_name); free(tmpdir_name); teardown_tmpdir_ = true; } void TearDown() override { if (teardown_tmpdir_) { rmdir(tmpdir.c_str()); } } private: bool teardown_tmpdir_; }; std::string capsicum_test_bindir; +// Adds a directory to $PATH. +static void AddDirectoryToPath(const char *dir) { + char *new_path, *old_path; + + old_path = getenv("PATH"); + assert(old_path); + + assert(asprintf(&new_path, "%s:%s", dir, old_path) > 0); + assert(setenv("PATH", new_path, 1) == 0); +} + int main(int argc, char* argv[]) { // Set up the test program path, so capsicum-test can find programs, like // mini-me* when executed from an absolute path. - { - char *new_path, *old_path, *program_name; - - program_name = strdup(argv[0]); - assert(program_name); - capsicum_test_bindir = std::string(dirname(program_name)); - free(program_name); + char *program_name; - old_path = getenv("PATH"); - assert(old_path); + // Copy argv[0], so dirname can do an in-place manipulation of the buffer's + // contents. + program_name = strdup(argv[0]); + assert(program_name); + capsicum_test_bindir = std::string(dirname(program_name)); + free(program_name); - assert(asprintf(&new_path, "%s:%s", capsicum_test_bindir.c_str(), - old_path) > 0); - assert(setenv("PATH", new_path, 1) == 0); - } + AddDirectoryToPath(capsicum_test_bindir.c_str()); ::testing::InitGoogleTest(&argc, argv); for (int ii = 1; ii < argc; ii++) { if (strcmp(argv[ii], "-v") == 0) { verbose = true; } else if (strcmp(argv[ii], "-T") == 0) { ii++; assert(ii < argc); tmpdir = argv[ii]; struct stat info; stat(tmpdir.c_str(), &info); assert(S_ISDIR(info.st_mode)); } else if (strcmp(argv[ii], "-t") == 0) { force_mt = true; } else if (strcmp(argv[ii], "-F") == 0) { force_nofork = true; } else if (strcmp(argv[ii], "-u") == 0) { if (++ii >= argc) { std::cerr << "-u needs argument" << std::endl; exit(1); } if (isdigit(argv[ii][0])) { other_uid = atoi(argv[ii]); } else { struct passwd *p = getpwnam(argv[ii]); if (!p) { std::cerr << "Failed to get entry for " << argv[ii] << ", errno=" << errno << std::endl; exit(1); } other_uid = p->pw_uid; } } } if (other_uid == 0) { struct stat info; if (stat(argv[0], &info) == 0) { other_uid = info.st_uid; } } #ifdef __linux__ // Check whether our temporary directory is on a tmpfs volume. struct statfs fsinfo; statfs(tmpdir.c_str(), &fsinfo); tmpdir_on_tmpfs = (fsinfo.f_type == TMPFS_MAGIC); #endif testing::AddGlobalTestEnvironment(new SetupEnvironment()); - int rc = RUN_ALL_TESTS(); - ShowSkippedTests(std::cerr); - return rc; + return RUN_ALL_TESTS(); } diff --git a/contrib/capsicum-test/capsicum-test.cc b/contrib/capsicum-test/capsicum-test.cc index 24b096ed877c..6adb222ec055 100644 --- a/contrib/capsicum-test/capsicum-test.cc +++ b/contrib/capsicum-test/capsicum-test.cc @@ -1,102 +1,78 @@ #include "capsicum-test.h" #include #include #include #include #include #include bool verbose = false; bool tmpdir_on_tmpfs = false; bool force_mt = false; bool force_nofork = false; uid_t other_uid = 0; namespace { std::map tmp_paths; } const char *TmpFile(const char *p) { std::string pathname(p); if (tmp_paths.find(pathname) == tmp_paths.end()) { std::string fullname = tmpdir + "/" + pathname; tmp_paths[pathname] = fullname; } return tmp_paths[pathname].c_str(); } char ProcessState(int pid) { #ifdef __linux__ // Open the process status file. char s[1024]; snprintf(s, sizeof(s), "/proc/%d/status", pid); FILE *f = fopen(s, "r"); if (f == NULL) return '\0'; // Read the file line by line looking for the state line. const char *prompt = "State:\t"; while (!feof(f)) { fgets(s, sizeof(s), f); if (!strncmp(s, prompt, strlen(prompt))) { fclose(f); return s[strlen(prompt)]; } } fclose(f); return '?'; #endif #ifdef __FreeBSD__ char buffer[1024]; snprintf(buffer, sizeof(buffer), "ps -p %d -o state | grep -v STAT", pid); sig_t original = signal(SIGCHLD, SIG_IGN); FILE* cmd = popen(buffer, "r"); usleep(50000); // allow any pending SIGCHLD signals to arrive signal(SIGCHLD, original); int result = fgetc(cmd); fclose(cmd); // Map FreeBSD codes to Linux codes. switch (result) { case EOF: return '\0'; case 'D': // disk wait case 'R': // runnable case 'S': // sleeping case 'T': // stopped case 'Z': // zombie return result; case 'W': // idle interrupt thread return 'S'; case 'I': // idle return 'S'; case 'L': // waiting to acquire lock default: return '?'; } #endif } - -typedef std::vector TestList; -typedef std::map SkippedTestMap; -static SkippedTestMap skipped_tests; -void TestSkipped(const char *testcase, const char *test, const std::string& reason) { - if (skipped_tests.find(reason) == skipped_tests.end()) { - skipped_tests[reason] = new TestList; - } - std::string testname(testcase); - testname += "."; - testname += test; - skipped_tests[reason]->push_back(testname); -} - -void ShowSkippedTests(std::ostream& os) { - for (SkippedTestMap::iterator skiplist = skipped_tests.begin(); - skiplist != skipped_tests.end(); ++skiplist) { - os << "Following tests were skipped because: " << skiplist->first << std::endl; - for (size_t ii = 0; ii < skiplist->second->size(); ++ii) { - const std::string& testname((*skiplist->second)[ii]); - os << " " << testname << std::endl; - } - } -} diff --git a/contrib/capsicum-test/capsicum-test.h b/contrib/capsicum-test/capsicum-test.h index 821100c48167..808840f4280e 100644 --- a/contrib/capsicum-test/capsicum-test.h +++ b/contrib/capsicum-test/capsicum-test.h @@ -1,261 +1,253 @@ /* -*- C++ -*- */ #ifndef CAPSICUM_TEST_H #define CAPSICUM_TEST_H #include #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; \ + int remaining_us = 30000000; \ 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) 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); \ } \ + if (result >= 0) { close(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); \ + if (result >= 0) { close(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); \ - GTEST_SKIP(); \ - } while (0) - // Mark a test that can only be run as root. -#define REQUIRE_ROOT() \ - if (getuid() != 0) { \ - TEST_SKIPPED("requires root"); \ - return; \ - } +#define GTEST_SKIP_IF_NOT_ROOT() \ + if (getuid() != 0) { GTEST_SKIP() << "requires root"; } + +extern std::string capsicum_test_bindir; #endif // CAPSICUM_TEST_H diff --git a/contrib/capsicum-test/fexecve.cc b/contrib/capsicum-test/fexecve.cc index 2212e9fd955e..86df2af06388 100644 --- a/contrib/capsicum-test/fexecve.cc +++ b/contrib/capsicum-test/fexecve.cc @@ -1,208 +1,207 @@ #include -#include #include +#include #include #include #include #include #include #include #include #include "syscalls.h" #include "capsicum.h" #include "capsicum-test.h" // Arguments to use in execve() calls. static char* null_envp[] = {NULL}; class Execve : public ::testing::Test { public: Execve() : exec_fd_(-1) { // We need a program to exec(), but for fexecve() to work in capability // mode that program needs to be statically linked (otherwise ld.so will // attempt to traverse the filesystem to load (e.g.) /lib/libc.so and // fail). exec_prog_ = capsicum_test_bindir + "/mini-me"; exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec"; exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid"; exec_fd_ = open(exec_prog_.c_str(), O_RDONLY); if (exec_fd_ < 0) { fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str()); } argv_checkroot_[0] = (char*)exec_prog_.c_str(); argv_fail_[0] = (char*)exec_prog_.c_str(); argv_pass_[0] = (char*)exec_prog_.c_str(); } ~Execve() { if (exec_fd_ >= 0) { close(exec_fd_); exec_fd_ = -1; } } protected: char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr}; char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr}; char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr}; std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_; int exec_fd_; }; class Fexecve : public Execve { public: Fexecve() : Execve() {} }; class FexecveWithScript : public Fexecve { public: FexecveWithScript() : Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {} void SetUp() override { // First, build an executable shell script int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755); EXPECT_OK(fd); const char* contents = "#!/bin/sh\nexit 99\n"; EXPECT_OK(write(fd, contents, strlen(contents))); close(fd); } void TearDown() override { (void)::unlink(temp_script_filename_); } const char *temp_script_filename_; }; FORK_TEST_F(Execve, BasicFexecve) { EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } FORK_TEST_F(Execve, InCapMode) { EXPECT_OK(cap_enter()); EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } FORK_TEST_F(Execve, FailWithoutCap) { EXPECT_OK(cap_enter()); int cap_fd = dup(exec_fd_); EXPECT_OK(cap_fd); cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp)); EXPECT_EQ(ENOTCAPABLE, errno); } FORK_TEST_F(Execve, SucceedWithCap) { EXPECT_OK(cap_enter()); int cap_fd = dup(exec_fd_); EXPECT_OK(cap_fd); cap_rights_t rights; // TODO(drysdale): would prefer that Linux Capsicum not need all of these // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); } FORK_TEST_F(Fexecve, ExecutePermissionCheck) { int fd = open(exec_prog_noexec_.c_str(), O_RDONLY); EXPECT_OK(fd); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp)); EXPECT_EQ(EACCES, errno); close(fd); } } -FORK_TEST_F(Fexecve, SetuidIgnored) { +FORK_TEST_F(Fexecve, SetuidIgnoredIfNonRoot) { if (geteuid() == 0) { - TEST_SKIPPED("requires non-root"); - return; + GTEST_SKIP() << "requires non-root"; } int fd = open(exec_prog_setuid_.c_str(), O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_enter()); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID); EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); close(fd); } } FORK_TEST_F(Fexecve, ExecveFailure) { EXPECT_OK(cap_enter()); EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp)); EXPECT_EQ(ECAPMODE, errno); } FORK_TEST_F(FexecveWithScript, CapModeScriptFail) { int fd; // Open the script file, with CAP_FEXECVE rights. fd = open(temp_script_filename_, O_RDONLY); cap_rights_t rights; cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); EXPECT_OK(cap_rights_limit(fd, &rights)); EXPECT_OK(cap_enter()); // Enter capability mode // Attempt fexecve; should fail, because "/bin/sh" is inaccessible. EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp)); } #ifdef HAVE_EXECVEAT class Execveat : public Execve { public: Execveat() : Execve() {} }; TEST_F(Execveat, NoUpwardTraversal) { - char *abspath = realpath(exec_prog_, NULL); + char *abspath = realpath(exec_prog_.c_str(), NULL); char cwd[1024]; getcwd(cwd, sizeof(cwd)); int dfd = open(".", O_DIRECTORY|O_RDONLY); pid_t child = fork(); if (child == 0) { EXPECT_OK(cap_enter()); // Enter capability mode. // Can't execveat() an absolute path, even relative to a dfd. EXPECT_SYSCALL_FAIL(ECAPMODE, execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0)); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, execveat(dfd, abspath, argv_pass_, null_envp, 0)); // Can't execveat() a relative path ("..//./"). char *p = cwd + strlen(cwd); while (*p != '/') p--; char buffer[1024] = "../"; strcat(buffer, ++p); strcat(buffer, "/"); - strcat(buffer, exec_prog_); + strcat(buffer, exec_prog_.c_str()); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, execveat(dfd, buffer, argv_pass_, null_envp, 0)); exit(HasFailure() ? 99 : 123); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; EXPECT_EQ(123, WEXITSTATUS(status)); free(abspath); close(dfd); } #endif diff --git a/contrib/capsicum-test/linux.cc b/contrib/capsicum-test/linux.cc index dee1f99897f6..81ba06c5e588 100644 --- a/contrib/capsicum-test/linux.cc +++ b/contrib/capsicum-test/linux.cc @@ -1,1503 +1,1500 @@ // Tests of Linux-specific functionality #ifdef __linux__ #include #include #include #include #include #include #include #include #include #include #include // Requires e.g. libcap-dev package for POSIX.1e capabilities headers #include #include #include #include #include #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" TEST(Linux, TimerFD) { int fd = timerfd_create(CLOCK_MONOTONIC, 0); cap_rights_t r_ro; cap_rights_init(&r_ro, CAP_READ); cap_rights_t r_wo; cap_rights_init(&r_wo, CAP_WRITE); cap_rights_t r_rw; cap_rights_init(&r_rw, CAP_READ, CAP_WRITE); cap_rights_t r_rwpoll; cap_rights_init(&r_rwpoll, CAP_READ, CAP_WRITE, CAP_EVENT); int cap_fd_ro = dup(fd); EXPECT_OK(cap_fd_ro); EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_ro)); int cap_fd_wo = dup(fd); EXPECT_OK(cap_fd_wo); EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_wo)); int cap_fd_rw = dup(fd); EXPECT_OK(cap_fd_rw); EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rw)); int cap_fd_all = dup(fd); EXPECT_OK(cap_fd_all); EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rwpoll)); struct itimerspec old_ispec; struct itimerspec ispec; ispec.it_interval.tv_sec = 0; ispec.it_interval.tv_nsec = 0; ispec.it_value.tv_sec = 0; ispec.it_value.tv_nsec = 100000000; // 100ms EXPECT_NOTCAPABLE(timerfd_settime(cap_fd_ro, 0, &ispec, NULL)); EXPECT_NOTCAPABLE(timerfd_settime(cap_fd_wo, 0, &ispec, &old_ispec)); EXPECT_OK(timerfd_settime(cap_fd_wo, 0, &ispec, NULL)); EXPECT_OK(timerfd_settime(cap_fd_rw, 0, &ispec, NULL)); EXPECT_OK(timerfd_settime(cap_fd_all, 0, &ispec, NULL)); EXPECT_NOTCAPABLE(timerfd_gettime(cap_fd_wo, &old_ispec)); EXPECT_OK(timerfd_gettime(cap_fd_ro, &old_ispec)); EXPECT_OK(timerfd_gettime(cap_fd_rw, &old_ispec)); EXPECT_OK(timerfd_gettime(cap_fd_all, &old_ispec)); // To be able to poll() for the timer pop, still need CAP_EVENT. struct pollfd poll_fd; for (int ii = 0; ii < 3; ii++) { poll_fd.revents = 0; poll_fd.events = POLLIN; switch (ii) { case 0: poll_fd.fd = cap_fd_ro; break; case 1: poll_fd.fd = cap_fd_wo; break; case 2: poll_fd.fd = cap_fd_rw; break; } // Poll immediately returns with POLLNVAL EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_EQ(0, (poll_fd.revents & POLLIN)); EXPECT_NE(0, (poll_fd.revents & POLLNVAL)); } poll_fd.fd = cap_fd_all; EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_NE(0, (poll_fd.revents & POLLIN)); EXPECT_EQ(0, (poll_fd.revents & POLLNVAL)); EXPECT_OK(timerfd_gettime(cap_fd_all, &old_ispec)); EXPECT_EQ(0, old_ispec.it_value.tv_sec); EXPECT_EQ(0, old_ispec.it_value.tv_nsec); EXPECT_EQ(0, old_ispec.it_interval.tv_sec); EXPECT_EQ(0, old_ispec.it_interval.tv_nsec); close(cap_fd_all); close(cap_fd_rw); close(cap_fd_wo); close(cap_fd_ro); close(fd); } -FORK_TEST(Linux, SignalFD) { +FORK_TEST(Linux, SignalFDIfSingleThreaded) { if (force_mt) { - TEST_SKIPPED("multi-threaded run clashes with signals"); - return; + GTEST_SKIP() << "multi-threaded run clashes with signals"; } pid_t me = getpid(); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGUSR1); // Block signals before registering against a new signal FD. EXPECT_OK(sigprocmask(SIG_BLOCK, &mask, NULL)); int fd = signalfd(-1, &mask, 0); EXPECT_OK(fd); cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_sig; cap_rights_init(&r_sig, CAP_FSIGNAL); cap_rights_t r_rssig; cap_rights_init(&r_rssig, CAP_FSIGNAL, CAP_READ, CAP_SEEK); cap_rights_t r_rssig_poll; cap_rights_init(&r_rssig_poll, CAP_FSIGNAL, CAP_READ, CAP_SEEK, CAP_EVENT); // Various capability variants. int cap_fd_none = dup(fd); EXPECT_OK(cap_fd_none); EXPECT_OK(cap_rights_limit(cap_fd_none, &r_ws)); int cap_fd_read = dup(fd); EXPECT_OK(cap_fd_read); EXPECT_OK(cap_rights_limit(cap_fd_read, &r_rs)); int cap_fd_sig = dup(fd); EXPECT_OK(cap_fd_sig); EXPECT_OK(cap_rights_limit(cap_fd_sig, &r_sig)); int cap_fd_sig_read = dup(fd); EXPECT_OK(cap_fd_sig_read); EXPECT_OK(cap_rights_limit(cap_fd_sig_read, &r_rssig)); int cap_fd_all = dup(fd); EXPECT_OK(cap_fd_all); EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rssig_poll)); struct signalfd_siginfo fdsi; // Need CAP_READ to read the signal information kill(me, SIGUSR1); EXPECT_NOTCAPABLE(read(cap_fd_none, &fdsi, sizeof(struct signalfd_siginfo))); EXPECT_NOTCAPABLE(read(cap_fd_sig, &fdsi, sizeof(struct signalfd_siginfo))); int len = read(cap_fd_read, &fdsi, sizeof(struct signalfd_siginfo)); EXPECT_OK(len); EXPECT_EQ(sizeof(struct signalfd_siginfo), (size_t)len); EXPECT_EQ(SIGUSR1, (int)fdsi.ssi_signo); // Need CAP_FSIGNAL to modify the signal mask. sigemptyset(&mask); sigaddset(&mask, SIGUSR1); sigaddset(&mask, SIGUSR2); EXPECT_OK(sigprocmask(SIG_BLOCK, &mask, NULL)); EXPECT_NOTCAPABLE(signalfd(cap_fd_none, &mask, 0)); EXPECT_NOTCAPABLE(signalfd(cap_fd_read, &mask, 0)); EXPECT_EQ(cap_fd_sig, signalfd(cap_fd_sig, &mask, 0)); // Need CAP_EVENT to get notification of a signal in poll(2). kill(me, SIGUSR2); struct pollfd poll_fd; poll_fd.revents = 0; poll_fd.events = POLLIN; poll_fd.fd = cap_fd_sig_read; EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_EQ(0, (poll_fd.revents & POLLIN)); EXPECT_NE(0, (poll_fd.revents & POLLNVAL)); poll_fd.fd = cap_fd_all; EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_NE(0, (poll_fd.revents & POLLIN)); EXPECT_EQ(0, (poll_fd.revents & POLLNVAL)); } TEST(Linux, EventFD) { int fd = eventfd(0, 0); EXPECT_OK(fd); cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_rwspoll; cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT); int cap_ro = dup(fd); EXPECT_OK(cap_ro); EXPECT_OK(cap_rights_limit(cap_ro, &r_rs)); int cap_wo = dup(fd); EXPECT_OK(cap_wo); EXPECT_OK(cap_rights_limit(cap_wo, &r_ws)); int cap_rw = dup(fd); EXPECT_OK(cap_rw); EXPECT_OK(cap_rights_limit(cap_rw, &r_rws)); int cap_all = dup(fd); EXPECT_OK(cap_all); EXPECT_OK(cap_rights_limit(cap_all, &r_rwspoll)); pid_t child = fork(); if (child == 0) { // Child: write counter to eventfd uint64_t u = 42; EXPECT_NOTCAPABLE(write(cap_ro, &u, sizeof(u))); EXPECT_OK(write(cap_wo, &u, sizeof(u))); exit(HasFailure()); } sleep(1); // Allow child to write struct pollfd poll_fd; poll_fd.revents = 0; poll_fd.events = POLLIN; poll_fd.fd = cap_rw; EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_EQ(0, (poll_fd.revents & POLLIN)); EXPECT_NE(0, (poll_fd.revents & POLLNVAL)); poll_fd.fd = cap_all; EXPECT_OK(poll(&poll_fd, 1, 400)); EXPECT_NE(0, (poll_fd.revents & POLLIN)); EXPECT_EQ(0, (poll_fd.revents & POLLNVAL)); uint64_t u; EXPECT_NOTCAPABLE(read(cap_wo, &u, sizeof(u))); EXPECT_OK(read(cap_ro, &u, sizeof(u))); EXPECT_EQ(42, (int)u); // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); close(cap_all); close(cap_rw); close(cap_wo); close(cap_ro); close(fd); } FORK_TEST(Linux, epoll) { int sock_fds[2]; EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds)); // Queue some data. char buffer[4] = {1, 2, 3, 4}; EXPECT_OK(write(sock_fds[1], buffer, sizeof(buffer))); EXPECT_OK(cap_enter()); // Enter capability mode. int epoll_fd = epoll_create(1); EXPECT_OK(epoll_fd); cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_rwspoll; cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT); cap_rights_t r_epoll; cap_rights_init(&r_epoll, CAP_EPOLL_CTL); int cap_epoll_wo = dup(epoll_fd); EXPECT_OK(cap_epoll_wo); EXPECT_OK(cap_rights_limit(cap_epoll_wo, &r_ws)); int cap_epoll_ro = dup(epoll_fd); EXPECT_OK(cap_epoll_ro); EXPECT_OK(cap_rights_limit(cap_epoll_ro, &r_rs)); int cap_epoll_rw = dup(epoll_fd); EXPECT_OK(cap_epoll_rw); EXPECT_OK(cap_rights_limit(cap_epoll_rw, &r_rws)); int cap_epoll_poll = dup(epoll_fd); EXPECT_OK(cap_epoll_poll); EXPECT_OK(cap_rights_limit(cap_epoll_poll, &r_rwspoll)); int cap_epoll_ctl = dup(epoll_fd); EXPECT_OK(cap_epoll_ctl); EXPECT_OK(cap_rights_limit(cap_epoll_ctl, &r_epoll)); // Can only modify the FDs being monitored if the CAP_EPOLL_CTL right is present. struct epoll_event eev; memset(&eev, 0, sizeof(eev)); eev.events = EPOLLIN|EPOLLOUT|EPOLLPRI; EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_ADD, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_ADD, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_ADD, sock_fds[0], &eev)); EXPECT_OK(epoll_ctl(cap_epoll_ctl, EPOLL_CTL_ADD, sock_fds[0], &eev)); eev.events = EPOLLIN|EPOLLOUT; EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_MOD, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_MOD, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_MOD, sock_fds[0], &eev)); EXPECT_OK(epoll_ctl(cap_epoll_ctl, EPOLL_CTL_MOD, sock_fds[0], &eev)); // Running epoll_pwait(2) requires CAP_EVENT. eev.events = 0; EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_ro, &eev, 1, 100, NULL)); EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_wo, &eev, 1, 100, NULL)); EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_rw, &eev, 1, 100, NULL)); EXPECT_OK(epoll_pwait(cap_epoll_poll, &eev, 1, 100, NULL)); EXPECT_EQ(EPOLLIN, eev.events & EPOLLIN); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_DEL, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_DEL, sock_fds[0], &eev)); EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_DEL, sock_fds[0], &eev)); EXPECT_OK(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fds[0], &eev)); close(cap_epoll_ctl); close(cap_epoll_poll); close(cap_epoll_rw); close(cap_epoll_ro); close(cap_epoll_wo); close(epoll_fd); close(sock_fds[1]); close(sock_fds[0]); } TEST(Linux, fstatat) { int fd = open(TmpFile("cap_fstatat"), O_CREAT|O_RDWR, 0644); EXPECT_OK(fd); unsigned char buffer[] = {1, 2, 3, 4}; EXPECT_OK(write(fd, buffer, sizeof(buffer))); cap_rights_t rights; int cap_rf = dup(fd); EXPECT_OK(cap_rf); EXPECT_OK(cap_rights_limit(cap_rf, cap_rights_init(&rights, CAP_READ, CAP_FSTAT))); int cap_ro = dup(fd); EXPECT_OK(cap_ro); EXPECT_OK(cap_rights_limit(cap_ro, cap_rights_init(&rights, CAP_READ))); struct stat info; EXPECT_OK(fstatat(fd, "", &info, AT_EMPTY_PATH)); EXPECT_NOTCAPABLE(fstatat(cap_ro, "", &info, AT_EMPTY_PATH)); EXPECT_OK(fstatat(cap_rf, "", &info, AT_EMPTY_PATH)); close(cap_ro); close(cap_rf); close(fd); int dir = open(tmpdir.c_str(), O_RDONLY); EXPECT_OK(dir); int dir_rf = dup(dir); EXPECT_OK(dir_rf); EXPECT_OK(cap_rights_limit(dir_rf, cap_rights_init(&rights, CAP_READ, CAP_FSTAT))); int dir_ro = dup(fd); EXPECT_OK(dir_ro); EXPECT_OK(cap_rights_limit(dir_ro, cap_rights_init(&rights, CAP_READ))); EXPECT_OK(fstatat(dir, "cap_fstatat", &info, AT_EMPTY_PATH)); EXPECT_NOTCAPABLE(fstatat(dir_ro, "cap_fstatat", &info, AT_EMPTY_PATH)); EXPECT_OK(fstatat(dir_rf, "cap_fstatat", &info, AT_EMPTY_PATH)); close(dir_ro); close(dir_rf); close(dir); unlink(TmpFile("cap_fstatat")); } // fanotify support may not be available at compile-time #ifdef __NR_fanotify_init -TEST(Linux, fanotify) { - REQUIRE_ROOT(); +TEST(Linux, FanotifyIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); int fa_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDWR); EXPECT_OK(fa_fd); if (fa_fd < 0) return; // May not be enabled cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_rwspoll; cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT); cap_rights_t r_rwsnotify; cap_rights_init(&r_rwsnotify, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_NOTIFY); cap_rights_t r_rsl; cap_rights_init(&r_rsl, CAP_READ, CAP_SEEK, CAP_LOOKUP); cap_rights_t r_rslstat; cap_rights_init(&r_rslstat, CAP_READ, CAP_SEEK, CAP_LOOKUP, CAP_FSTAT); cap_rights_t r_rsstat; cap_rights_init(&r_rsstat, CAP_READ, CAP_SEEK, CAP_FSTAT); int cap_fd_ro = dup(fa_fd); EXPECT_OK(cap_fd_ro); EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_rs)); int cap_fd_wo = dup(fa_fd); EXPECT_OK(cap_fd_wo); EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_ws)); int cap_fd_rw = dup(fa_fd); EXPECT_OK(cap_fd_rw); EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rws)); int cap_fd_poll = dup(fa_fd); EXPECT_OK(cap_fd_poll); EXPECT_OK(cap_rights_limit(cap_fd_poll, &r_rwspoll)); int cap_fd_not = dup(fa_fd); EXPECT_OK(cap_fd_not); EXPECT_OK(cap_rights_limit(cap_fd_not, &r_rwsnotify)); int rc = mkdir(TmpFile("cap_notify"), 0755); EXPECT_TRUE(rc == 0 || errno == EEXIST); int dfd = open(TmpFile("cap_notify"), O_RDONLY); EXPECT_OK(dfd); int fd = open(TmpFile("cap_notify/file"), O_CREAT|O_RDWR, 0644); close(fd); int cap_dfd = dup(dfd); EXPECT_OK(cap_dfd); EXPECT_OK(cap_rights_limit(cap_dfd, &r_rslstat)); EXPECT_OK(cap_dfd); int cap_dfd_rs = dup(dfd); EXPECT_OK(cap_dfd_rs); EXPECT_OK(cap_rights_limit(cap_dfd_rs, &r_rs)); EXPECT_OK(cap_dfd_rs); int cap_dfd_rsstat = dup(dfd); EXPECT_OK(cap_dfd_rsstat); EXPECT_OK(cap_rights_limit(cap_dfd_rsstat, &r_rsstat)); EXPECT_OK(cap_dfd_rsstat); int cap_dfd_rsl = dup(dfd); EXPECT_OK(cap_dfd_rsl); EXPECT_OK(cap_rights_limit(cap_dfd_rsl, &r_rsl)); EXPECT_OK(cap_dfd_rsl); // Need CAP_NOTIFY to change what's monitored. EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_ro, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL)); EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_wo, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL)); EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_rw, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL)); EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL)); // Need CAP_FSTAT on the thing monitored. EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd_rs, NULL)); EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd_rsstat, NULL)); // Too add monitoring of a file under a dfd, need CAP_LOOKUP|CAP_FSTAT on the dfd. EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd_rsstat, "file")); EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd_rsl, "file")); EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd, "file")); pid_t child = fork(); if (child == 0) { // Child: Perform activity in the directory under notify. sleep(1); unlink(TmpFile("cap_notify/temp")); int fd = open(TmpFile("cap_notify/temp"), O_CREAT|O_RDWR, 0644); close(fd); exit(0); } // Need CAP_EVENT to poll. struct pollfd poll_fd; poll_fd.revents = 0; poll_fd.events = POLLIN; poll_fd.fd = cap_fd_rw; EXPECT_OK(poll(&poll_fd, 1, 1400)); EXPECT_EQ(0, (poll_fd.revents & POLLIN)); EXPECT_NE(0, (poll_fd.revents & POLLNVAL)); poll_fd.fd = cap_fd_not; EXPECT_OK(poll(&poll_fd, 1, 1400)); EXPECT_EQ(0, (poll_fd.revents & POLLIN)); EXPECT_NE(0, (poll_fd.revents & POLLNVAL)); poll_fd.fd = cap_fd_poll; EXPECT_OK(poll(&poll_fd, 1, 1400)); EXPECT_NE(0, (poll_fd.revents & POLLIN)); EXPECT_EQ(0, (poll_fd.revents & POLLNVAL)); // Need CAP_READ to read. struct fanotify_event_metadata ev; memset(&ev, 0, sizeof(ev)); EXPECT_NOTCAPABLE(read(cap_fd_wo, &ev, sizeof(ev))); rc = read(fa_fd, &ev, sizeof(ev)); EXPECT_OK(rc); EXPECT_EQ((int)sizeof(struct fanotify_event_metadata), rc); EXPECT_EQ(child, ev.pid); EXPECT_NE(0, ev.fd); // TODO(drysdale): reinstate if/when capsicum-linux propagates rights // to fanotify-generated FDs. #ifdef OMIT // fanotify(7) gives us a FD for the changed file. This should // only have rights that are a subset of those for the original // monitored directory file descriptor. cap_rights_t rights; CAP_SET_ALL(&rights); EXPECT_OK(cap_rights_get(ev.fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_rslstat); #endif // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); close(cap_dfd_rsstat); close(cap_dfd_rsl); close(cap_dfd_rs); close(cap_dfd); close(dfd); unlink(TmpFile("cap_notify/file")); unlink(TmpFile("cap_notify/temp")); rmdir(TmpFile("cap_notify")); close(cap_fd_not); close(cap_fd_poll); close(cap_fd_rw); close(cap_fd_wo); close(cap_fd_ro); close(fa_fd); } #endif TEST(Linux, inotify) { int i_fd = inotify_init(); EXPECT_OK(i_fd); cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_rws; cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK); cap_rights_t r_rwsnotify; cap_rights_init(&r_rwsnotify, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_NOTIFY); int cap_fd_ro = dup(i_fd); EXPECT_OK(cap_fd_ro); EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_rs)); int cap_fd_wo = dup(i_fd); EXPECT_OK(cap_fd_wo); EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_ws)); int cap_fd_rw = dup(i_fd); EXPECT_OK(cap_fd_rw); EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rws)); int cap_fd_all = dup(i_fd); EXPECT_OK(cap_fd_all); EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rwsnotify)); int fd = open(TmpFile("cap_inotify"), O_CREAT|O_RDWR, 0644); EXPECT_NOTCAPABLE(inotify_add_watch(cap_fd_rw, TmpFile("cap_inotify"), IN_ACCESS|IN_MODIFY)); int wd = inotify_add_watch(i_fd, TmpFile("cap_inotify"), IN_ACCESS|IN_MODIFY); EXPECT_OK(wd); unsigned char buffer[] = {1, 2, 3, 4}; EXPECT_OK(write(fd, buffer, sizeof(buffer))); struct inotify_event iev; memset(&iev, 0, sizeof(iev)); EXPECT_NOTCAPABLE(read(cap_fd_wo, &iev, sizeof(iev))); int rc = read(cap_fd_ro, &iev, sizeof(iev)); EXPECT_OK(rc); EXPECT_EQ((int)sizeof(iev), rc); EXPECT_EQ(wd, iev.wd); EXPECT_NOTCAPABLE(inotify_rm_watch(cap_fd_wo, wd)); EXPECT_OK(inotify_rm_watch(cap_fd_all, wd)); close(fd); close(cap_fd_all); close(cap_fd_rw); close(cap_fd_wo); close(cap_fd_ro); close(i_fd); unlink(TmpFile("cap_inotify")); } -TEST(Linux, ArchChange) { +TEST(Linux, ArchChangeIfAvailable) { const char* prog_candidates[] = {"./mini-me.32", "./mini-me.x32", "./mini-me.64"}; const char* progs[] = {NULL, NULL, NULL}; char* argv_pass[] = {(char*)"to-come", (char*)"--capmode", NULL}; char* null_envp[] = {NULL}; int fds[3]; int count = 0; for (int ii = 0; ii < 3; ii++) { fds[count] = open(prog_candidates[ii], O_RDONLY); if (fds[count] >= 0) { progs[count] = prog_candidates[ii]; count++; } } if (count == 0) { - TEST_SKIPPED("no different-architecture programs available"); - return; + GTEST_SKIP() << "no different-architecture programs available"; } for (int ii = 0; ii < count; ii++) { // Fork-and-exec a binary of this architecture. pid_t child = fork(); if (child == 0) { EXPECT_OK(cap_enter()); // Enter capability mode if (verbose) fprintf(stderr, "[%d] call fexecve(%s, %s)\n", getpid_(), progs[ii], argv_pass[1]); argv_pass[0] = (char *)progs[ii]; int rc = fexecve_(fds[ii], argv_pass, null_envp); fprintf(stderr, "fexecve(%s) returned %d errno %d\n", progs[ii], rc, errno); exit(99); // Should not reach here. } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); close(fds[ii]); } } -FORK_TEST(Linux, Namespace) { - REQUIRE_ROOT(); +FORK_TEST(Linux, NamespaceIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); pid_t me = getpid_(); // Create a new UTS namespace. EXPECT_OK(unshare(CLONE_NEWUTS)); // Open an FD to its symlink. char buffer[256]; sprintf(buffer, "/proc/%d/ns/uts", me); int ns_fd = open(buffer, O_RDONLY); cap_rights_t r_rwlstat; cap_rights_init(&r_rwlstat, CAP_READ, CAP_WRITE, CAP_LOOKUP, CAP_FSTAT); cap_rights_t r_rwlstatns; cap_rights_init(&r_rwlstatns, CAP_READ, CAP_WRITE, CAP_LOOKUP, CAP_FSTAT, CAP_SETNS); int cap_fd = dup(ns_fd); EXPECT_OK(cap_fd); EXPECT_OK(cap_rights_limit(cap_fd, &r_rwlstat)); int cap_fd_setns = dup(ns_fd); EXPECT_OK(cap_fd_setns); EXPECT_OK(cap_rights_limit(cap_fd_setns, &r_rwlstatns)); EXPECT_NOTCAPABLE(setns(cap_fd, CLONE_NEWUTS)); EXPECT_OK(setns(cap_fd_setns, CLONE_NEWUTS)); EXPECT_OK(cap_enter()); // Enter capability mode. // No setns(2) but unshare(2) is allowed. EXPECT_CAPMODE(setns(ns_fd, CLONE_NEWUTS)); EXPECT_OK(unshare(CLONE_NEWUTS)); } static void SendFD(int fd, int over) { 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 = CMSG_LEN(sizeof(int)); struct cmsghdr *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) = fd; buffer1[0] = 0; iov[0].iov_len = 1; int rc = sendmsg(over, &mh, 0); EXPECT_OK(rc); } static int ReceiveFD(int over) { 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); int rc = recvmsg(over, &mh, 0); EXPECT_OK(rc); EXPECT_LE(CMSG_LEN(sizeof(int)), mh.msg_controllen); struct cmsghdr *cmptr = CMSG_FIRSTHDR(&mh); int fd = *(int*)CMSG_DATA(cmptr); EXPECT_EQ(CMSG_LEN(sizeof(int)), cmptr->cmsg_len); cmptr = CMSG_NXTHDR(&mh, cmptr); EXPECT_TRUE(cmptr == NULL); return fd; } static int shared_pd = -1; static int shared_sock_fds[2]; static int ChildFunc(void *arg) { // This function is running in a new PID namespace, and so is pid 1. if (verbose) fprintf(stderr, " ChildFunc: pid=%d, ppid=%d\n", getpid_(), getppid()); EXPECT_EQ(1, getpid_()); EXPECT_EQ(0, getppid()); // The shared process descriptor is outside our namespace, so we cannot // get its pid. if (verbose) fprintf(stderr, " ChildFunc: shared_pd=%d\n", shared_pd); pid_t shared_child = -1; EXPECT_OK(pdgetpid(shared_pd, &shared_child)); if (verbose) fprintf(stderr, " ChildFunc: corresponding pid=%d\n", shared_child); EXPECT_EQ(0, shared_child); // But we can pdkill() it even so. if (verbose) fprintf(stderr, " ChildFunc: call pdkill(pd=%d)\n", shared_pd); EXPECT_OK(pdkill(shared_pd, SIGINT)); int pd; pid_t child = pdfork(&pd, 0); EXPECT_OK(child); if (child == 0) { // Child: expect pid 2. if (verbose) fprintf(stderr, " child of ChildFunc: pid=%d, ppid=%d\n", getpid_(), getppid()); EXPECT_EQ(2, getpid_()); EXPECT_EQ(1, getppid()); while (true) { if (verbose) fprintf(stderr, " child of ChildFunc: \"I aten't dead\"\n"); sleep(1); } exit(0); } EXPECT_EQ(2, child); EXPECT_PID_ALIVE(child); if (verbose) fprintf(stderr, " ChildFunc: pdfork() -> pd=%d, corresponding pid=%d state='%c'\n", pd, child, ProcessState(child)); pid_t pid; EXPECT_OK(pdgetpid(pd, &pid)); EXPECT_EQ(child, pid); sleep(2); // Send the process descriptor over UNIX domain socket back to parent. SendFD(pd, shared_sock_fds[1]); // Wait for death of (grand)child, killed by our parent. if (verbose) fprintf(stderr, " ChildFunc: wait on pid=%d\n", child); int status; EXPECT_EQ(child, wait4(child, &status, __WALL, NULL)); if (verbose) fprintf(stderr, " ChildFunc: return 0\n"); return 0; } #define STACK_SIZE (1024 * 1024) static char child_stack[STACK_SIZE]; -// TODO(drysdale): fork into a user namespace first so REQUIRE_ROOT can be removed. -TEST(Linux, PidNamespacePdFork) { - REQUIRE_ROOT(); +// TODO(drysdale): fork into a user namespace first so GTEST_SKIP_IF_NOT_ROOT can be removed. +TEST(Linux, PidNamespacePdForkIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); // Pass process descriptors in both directions across a PID namespace boundary. // pdfork() off a child before we start, holding its process descriptor in a global // variable that's accessible to children. pid_t firstborn = pdfork(&shared_pd, 0); EXPECT_OK(firstborn); if (firstborn == 0) { while (true) { if (verbose) fprintf(stderr, " Firstborn: \"I aten't dead\"\n"); sleep(1); } exit(0); } EXPECT_PID_ALIVE(firstborn); if (verbose) fprintf(stderr, "Parent: pre-pdfork()ed pd=%d, pid=%d state='%c'\n", shared_pd, firstborn, ProcessState(firstborn)); sleep(2); // Prepare sockets to communicate with child process. EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds)); // Clone into a child process with a new pid namespace. pid_t child = clone(ChildFunc, child_stack + STACK_SIZE, CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL); EXPECT_OK(child); EXPECT_PID_ALIVE(child); if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child)); // Ensure the child runs. First thing it does is to kill our firstborn, using shared_pd. sleep(1); EXPECT_PID_DEAD(firstborn); // But we can still retrieve firstborn's PID, as it's not been reaped yet. pid_t child0; EXPECT_OK(pdgetpid(shared_pd, &child0)); EXPECT_EQ(firstborn, child0); if (verbose) fprintf(stderr, "Parent: check on firstborn: pdgetpid(pd=%d) -> child=%d state='%c'\n", shared_pd, child0, ProcessState(child0)); // Now reap it. int status; EXPECT_EQ(firstborn, waitpid(firstborn, &status, __WALL)); // Get the process descriptor of the child-of-child via socket transfer. int grandchild_pd = ReceiveFD(shared_sock_fds[0]); // Our notion of the pid associated with the grandchild is in the main PID namespace. pid_t grandchild; EXPECT_OK(pdgetpid(grandchild_pd, &grandchild)); EXPECT_NE(2, grandchild); if (verbose) fprintf(stderr, "Parent: pre-pdkill: pdgetpid(grandchild_pd=%d) -> grandchild=%d state='%c'\n", grandchild_pd, grandchild, ProcessState(grandchild)); EXPECT_PID_ALIVE(grandchild); // Kill the grandchild via the process descriptor. EXPECT_OK(pdkill(grandchild_pd, SIGINT)); usleep(10000); if (verbose) fprintf(stderr, "Parent: post-pdkill: pdgetpid(grandchild_pd=%d) -> grandchild=%d state='%c'\n", grandchild_pd, grandchild, ProcessState(grandchild)); EXPECT_PID_DEAD(grandchild); sleep(2); // Wait for the child. EXPECT_EQ(child, waitpid(child, &status, WNOHANG)); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); close(shared_sock_fds[0]); close(shared_sock_fds[1]); close(shared_pd); close(grandchild_pd); } int NSInit(void *data) { // This function is running in a new PID namespace, and so is pid 1. if (verbose) fprintf(stderr, " NSInit: pid=%d, ppid=%d\n", getpid_(), getppid()); EXPECT_EQ(1, getpid_()); EXPECT_EQ(0, getppid()); int pd; pid_t child = pdfork(&pd, 0); EXPECT_OK(child); if (child == 0) { // Child: loop forever until terminated. if (verbose) fprintf(stderr, " child of NSInit: pid=%d, ppid=%d\n", getpid_(), getppid()); while (true) { if (verbose) fprintf(stderr, " child of NSInit: \"I aten't dead\"\n"); usleep(100000); } exit(0); } EXPECT_EQ(2, child); EXPECT_PID_ALIVE(child); if (verbose) fprintf(stderr, " NSInit: pdfork() -> pd=%d, corresponding pid=%d state='%c'\n", pd, child, ProcessState(child)); sleep(1); // Send the process descriptor over UNIX domain socket back to parent. SendFD(pd, shared_sock_fds[1]); close(pd); // Wait for a byte back in the other direction. int value; if (verbose) fprintf(stderr, " NSInit: block waiting for value\n"); read(shared_sock_fds[1], &value, sizeof(value)); if (verbose) fprintf(stderr, " NSInit: return 0\n"); return 0; } -TEST(Linux, DeadNSInit) { - REQUIRE_ROOT(); +TEST(Linux, DeadNSInitIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); // Prepare sockets to communicate with child process. EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds)); // Clone into a child process with a new pid namespace. pid_t child = clone(NSInit, child_stack + STACK_SIZE, CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL); usleep(10000); EXPECT_OK(child); EXPECT_PID_ALIVE(child); if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child)); // Get the process descriptor of the child-of-child via socket transfer. int grandchild_pd = ReceiveFD(shared_sock_fds[0]); pid_t grandchild; EXPECT_OK(pdgetpid(grandchild_pd, &grandchild)); if (verbose) fprintf(stderr, "Parent: grandchild is %d state='%c'\n", grandchild, ProcessState(grandchild)); // Send an int to the child to trigger its termination. Grandchild should also // go, as its init process is gone. int zero = 0; if (verbose) fprintf(stderr, "Parent: write 0 to pipe\n"); write(shared_sock_fds[0], &zero, sizeof(zero)); EXPECT_PID_ZOMBIE(child); EXPECT_PID_GONE(grandchild); // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, WNOHANG)); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); EXPECT_PID_GONE(child); close(shared_sock_fds[0]); close(shared_sock_fds[1]); close(grandchild_pd); if (verbose) { fprintf(stderr, "Parent: child %d in state='%c'\n", child, ProcessState(child)); fprintf(stderr, "Parent: grandchild %d in state='%c'\n", grandchild, ProcessState(grandchild)); } } -TEST(Linux, DeadNSInit2) { - REQUIRE_ROOT(); +TEST(Linux, DeadNSInit2IfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); // Prepare sockets to communicate with child process. EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds)); // Clone into a child process with a new pid namespace. pid_t child = clone(NSInit, child_stack + STACK_SIZE, CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL); usleep(10000); EXPECT_OK(child); EXPECT_PID_ALIVE(child); if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child)); // Get the process descriptor of the child-of-child via socket transfer. int grandchild_pd = ReceiveFD(shared_sock_fds[0]); pid_t grandchild; EXPECT_OK(pdgetpid(grandchild_pd, &grandchild)); if (verbose) fprintf(stderr, "Parent: grandchild is %d state='%c'\n", grandchild, ProcessState(grandchild)); // Kill the grandchild EXPECT_OK(pdkill(grandchild_pd, SIGINT)); usleep(10000); EXPECT_PID_ZOMBIE(grandchild); // Close the process descriptor, so there are now no procdesc references to grandchild. close(grandchild_pd); // Send an int to the child to trigger its termination. Grandchild should also // go, as its init process is gone. int zero = 0; if (verbose) fprintf(stderr, "Parent: write 0 to pipe\n"); write(shared_sock_fds[0], &zero, sizeof(zero)); EXPECT_PID_ZOMBIE(child); EXPECT_PID_GONE(grandchild); // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, WNOHANG)); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); close(shared_sock_fds[0]); close(shared_sock_fds[1]); if (verbose) { fprintf(stderr, "Parent: child %d in state='%c'\n", child, ProcessState(child)); fprintf(stderr, "Parent: grandchild %d in state='%c'\n", grandchild, ProcessState(grandchild)); } } #ifdef __x86_64__ FORK_TEST(Linux, CheckHighWord) { EXPECT_OK(cap_enter()); // Enter capability mode. int rc = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // no_new_privs = 1 // Set some of the high 32-bits of argument zero. uint64_t big_cmd = PR_GET_NO_NEW_PRIVS | 0x100000000LL; EXPECT_CAPMODE(syscall(__NR_prctl, big_cmd, 0, 0, 0, 0)); } #endif FORK_TEST(Linux, PrctlOpenatBeneath) { // Set no_new_privs = 1 EXPECT_OK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); int rc = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // no_new_privs = 1 // Set openat-beneath mode EXPECT_OK(prctl(PR_SET_OPENAT_BENEATH, 1, 0, 0, 0)); rc = prctl(PR_GET_OPENAT_BENEATH, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // openat_beneath = 1 // Clear openat-beneath mode EXPECT_OK(prctl(PR_SET_OPENAT_BENEATH, 0, 0, 0, 0)); rc = prctl(PR_GET_OPENAT_BENEATH, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(0, rc); // openat_beneath = 0 EXPECT_OK(cap_enter()); // Enter capability mode // Expect to be in openat_beneath mode rc = prctl(PR_GET_OPENAT_BENEATH, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // openat_beneath = 1 // Expect this to be immutable. EXPECT_CAPMODE(prctl(PR_SET_OPENAT_BENEATH, 0, 0, 0, 0)); rc = prctl(PR_GET_OPENAT_BENEATH, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // openat_beneath = 1 } FORK_TEST(Linux, NoNewPrivs) { if (getuid() == 0) { // If root, drop CAP_SYS_ADMIN POSIX.1e capability. struct __user_cap_header_struct hdr; hdr.version = _LINUX_CAPABILITY_VERSION_3; hdr.pid = getpid_(); struct __user_cap_data_struct data[3]; EXPECT_OK(capget(&hdr, &data[0])); data[0].effective &= ~(1 << CAP_SYS_ADMIN); data[0].permitted &= ~(1 << CAP_SYS_ADMIN); data[0].inheritable &= ~(1 << CAP_SYS_ADMIN); EXPECT_OK(capset(&hdr, &data[0])); } int rc = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(0, rc); // no_new_privs == 0 // Can't enter seccomp-bpf mode with no_new_privs == 0 struct sock_filter filter[] = { BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) }; struct sock_fprog bpf; bpf.len = (sizeof(filter) / sizeof(filter[0])); bpf.filter = filter; rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bpf, 0, 0); EXPECT_EQ(-1, rc); EXPECT_EQ(EACCES, errno); // Set no_new_privs = 1 EXPECT_OK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); rc = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); EXPECT_OK(rc); EXPECT_EQ(1, rc); // no_new_privs = 1 // Can now turn on seccomp mode EXPECT_OK(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bpf, 0, 0)); } /* Macros for BPF generation */ #define BPF_RETURN_ERRNO(err) \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO | (err & 0xFFFF)) #define BPF_KILL_PROCESS \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) #define BPF_ALLOW \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) #define EXAMINE_SYSCALL \ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)) #define ALLOW_SYSCALL(name) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ BPF_ALLOW #define KILL_SYSCALL(name) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ BPF_KILL_PROCESS #define FAIL_SYSCALL(name, err) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ BPF_RETURN_ERRNO(err) TEST(Linux, CapModeWithBPF) { pid_t child = fork(); EXPECT_OK(child); if (child == 0) { int fd = open(TmpFile("cap_bpf_capmode"), O_CREAT|O_RDWR, 0644); cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FSYNC); EXPECT_OK(cap_rights_limit(fd, &rights)); struct sock_filter filter[] = { EXAMINE_SYSCALL, FAIL_SYSCALL(fchmod, ENOMEM), FAIL_SYSCALL(fstat, ENOEXEC), ALLOW_SYSCALL(close), KILL_SYSCALL(fsync), BPF_ALLOW }; struct sock_fprog bpf = {.len = (sizeof(filter) / sizeof(filter[0])), .filter = filter}; // Set up seccomp-bpf first. EXPECT_OK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); EXPECT_OK(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bpf, 0, 0)); EXPECT_OK(cap_enter()); // Enter capability mode. // fchmod is allowed by Capsicum, but failed by BPF. EXPECT_SYSCALL_FAIL(ENOMEM, fchmod(fd, 0644)); // open is allowed by BPF, but failed by Capsicum EXPECT_SYSCALL_FAIL(ECAPMODE, open(TmpFile("cap_bpf_capmode"), O_RDONLY)); // fstat is failed by both BPF and Capsicum; tie-break is on errno struct stat buf; EXPECT_SYSCALL_FAIL(ENOEXEC, fstat(fd, &buf)); // fsync is allowed by Capsicum, but BPF's SIGSYS generation take precedence fsync(fd); // terminate with unhandled SIGSYS exit(0); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFSIGNALED(status)); EXPECT_EQ(SIGSYS, WTERMSIG(status)); unlink(TmpFile("cap_bpf_capmode")); } TEST(Linux, AIO) { int fd = open(TmpFile("cap_aio"), O_CREAT|O_RDWR, 0644); EXPECT_OK(fd); cap_rights_t r_rs; cap_rights_init(&r_rs, CAP_READ, CAP_SEEK); cap_rights_t r_ws; cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK); cap_rights_t r_rwssync; cap_rights_init(&r_rwssync, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FSYNC); int cap_ro = dup(fd); EXPECT_OK(cap_ro); EXPECT_OK(cap_rights_limit(cap_ro, &r_rs)); EXPECT_OK(cap_ro); int cap_wo = dup(fd); EXPECT_OK(cap_wo); EXPECT_OK(cap_rights_limit(cap_wo, &r_ws)); EXPECT_OK(cap_wo); int cap_all = dup(fd); EXPECT_OK(cap_all); EXPECT_OK(cap_rights_limit(cap_all, &r_rwssync)); EXPECT_OK(cap_all); // Linux: io_setup, io_submit, io_getevents, io_cancel, io_destroy aio_context_t ctx = 0; EXPECT_OK(syscall(__NR_io_setup, 10, &ctx)); unsigned char buffer[32] = {1, 2, 3, 4}; struct iocb req; memset(&req, 0, sizeof(req)); req.aio_reqprio = 0; req.aio_fildes = fd; uintptr_t bufaddr = (uintptr_t)buffer; req.aio_buf = (__u64)bufaddr; req.aio_nbytes = 4; req.aio_offset = 0; struct iocb* reqs[1] = {&req}; // Write operation req.aio_lio_opcode = IOCB_CMD_PWRITE; req.aio_fildes = cap_ro; EXPECT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); req.aio_fildes = cap_wo; EXPECT_OK(syscall(__NR_io_submit, ctx, 1, reqs)); // Sync operation req.aio_lio_opcode = IOCB_CMD_FSYNC; EXPECT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); req.aio_lio_opcode = IOCB_CMD_FDSYNC; EXPECT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); // Even with CAP_FSYNC, turns out fsync/fdsync aren't implemented req.aio_fildes = cap_all; EXPECT_FAIL_NOT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); req.aio_lio_opcode = IOCB_CMD_FSYNC; EXPECT_FAIL_NOT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); // Read operation req.aio_lio_opcode = IOCB_CMD_PREAD; req.aio_fildes = cap_wo; EXPECT_NOTCAPABLE(syscall(__NR_io_submit, ctx, 1, reqs)); req.aio_fildes = cap_ro; EXPECT_OK(syscall(__NR_io_submit, ctx, 1, reqs)); EXPECT_OK(syscall(__NR_io_destroy, ctx)); close(cap_all); close(cap_wo); close(cap_ro); close(fd); unlink(TmpFile("cap_aio")); } #ifndef KCMP_FILE #define KCMP_FILE 0 #endif -TEST(Linux, Kcmp) { +TEST(Linux, KcmpIfAvailable) { // This requires CONFIG_CHECKPOINT_RESTORE in kernel config. int fd = open("/etc/passwd", O_RDONLY); EXPECT_OK(fd); pid_t parent = getpid_(); errno = 0; int rc = syscall(__NR_kcmp, parent, parent, KCMP_FILE, fd, fd); if (rc == -1 && errno == ENOSYS) { - TEST_SKIPPED("kcmp(2) gives -ENOSYS"); - return; + GTEST_SKIP() << "kcmp(2) gives -ENOSYS"; } pid_t child = fork(); if (child == 0) { // Child: limit rights on FD. child = getpid_(); EXPECT_OK(syscall(__NR_kcmp, parent, child, KCMP_FILE, fd, fd)); cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE); EXPECT_OK(cap_rights_limit(fd, &rights)); // A capability wrapping a normal FD is different (from a kcmp(2) perspective) // than the original file. EXPECT_NE(0, syscall(__NR_kcmp, parent, child, KCMP_FILE, fd, fd)); 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); close(fd); } TEST(Linux, ProcFS) { cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_SEEK); int fd = open("/etc/passwd", O_RDONLY); EXPECT_OK(fd); lseek(fd, 4, SEEK_SET); int cap = dup(fd); EXPECT_OK(cap); EXPECT_OK(cap_rights_limit(cap, &rights)); pid_t me = getpid_(); char buffer[1024]; sprintf(buffer, "/proc/%d/fdinfo/%d", me, cap); int procfd = open(buffer, O_RDONLY); EXPECT_OK(procfd) << " failed to open " << buffer; if (procfd < 0) return; int proccap = dup(procfd); EXPECT_OK(proccap); EXPECT_OK(cap_rights_limit(proccap, &rights)); EXPECT_OK(read(proccap, buffer, sizeof(buffer))); // The fdinfo should include the file pos of the underlying file EXPECT_NE((char*)NULL, strstr(buffer, "pos:\t4")); // ...and the rights of the Capsicum capability. EXPECT_NE((char*)NULL, strstr(buffer, "rights:\t0x")); close(procfd); close(proccap); close(cap); close(fd); } FORK_TEST(Linux, ProcessClocks) { pid_t self = getpid_(); pid_t child = fork(); EXPECT_OK(child); if (child == 0) { child = getpid_(); usleep(100000); exit(0); } EXPECT_OK(cap_enter()); // Enter capability mode. // Nefariously build a clock ID for the child's CPU time. // This relies on knowledge of the internal layout of clock IDs. clockid_t child_clock; child_clock = ((~child) << 3) | 0x0; struct timespec ts; memset(&ts, 0, sizeof(ts)); // TODO(drysdale): Should not be possible to retrieve info about a // different process, as the PID global namespace should be locked // down. EXPECT_OK(clock_gettime(child_clock, &ts)); if (verbose) fprintf(stderr, "[parent: %d] clock_gettime(child=%d->0x%08x) is %ld.%09ld \n", self, child, child_clock, (long)ts.tv_sec, (long)ts.tv_nsec); child_clock = ((~1) << 3) | 0x0; memset(&ts, 0, sizeof(ts)); EXPECT_OK(clock_gettime(child_clock, &ts)); if (verbose) fprintf(stderr, "[parent: %d] clock_gettime(init=1->0x%08x) is %ld.%09ld \n", self, child_clock, (long)ts.tv_sec, (long)ts.tv_nsec); // Orphan the child. } TEST(Linux, SetLease) { int fd_all = open(TmpFile("cap_lease"), O_CREAT|O_RDWR, 0644); EXPECT_OK(fd_all); int fd_rw = dup(fd_all); EXPECT_OK(fd_rw); cap_rights_t r_all; cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_FLOCK, CAP_FSIGNAL); EXPECT_OK(cap_rights_limit(fd_all, &r_all)); cap_rights_t r_rw; cap_rights_init(&r_rw, CAP_READ, CAP_WRITE); EXPECT_OK(cap_rights_limit(fd_rw, &r_rw)); EXPECT_NOTCAPABLE(fcntl(fd_rw, F_SETLEASE, F_WRLCK)); EXPECT_NOTCAPABLE(fcntl(fd_rw, F_GETLEASE)); if (!tmpdir_on_tmpfs) { // tmpfs doesn't support leases EXPECT_OK(fcntl(fd_all, F_SETLEASE, F_WRLCK)); EXPECT_EQ(F_WRLCK, fcntl(fd_all, F_GETLEASE)); EXPECT_OK(fcntl(fd_all, F_SETLEASE, F_UNLCK, 0)); EXPECT_EQ(F_UNLCK, fcntl(fd_all, F_GETLEASE)); } close(fd_all); close(fd_rw); unlink(TmpFile("cap_lease")); } TEST(Linux, InvalidRightsSyscall) { int fd = open(TmpFile("cap_invalid_rights"), O_RDONLY|O_CREAT, 0644); EXPECT_OK(fd); cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FCHMOD, CAP_FSTAT); // Use the raw syscall throughout. EXPECT_EQ(0, syscall(__NR_cap_rights_limit, fd, &rights, 0, 0, NULL, 0)); // Directly access the syscall, and find all unseemly manner of use for it. // - Invalid flags EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, 0, NULL, 1)); EXPECT_EQ(EINVAL, errno); // - Specify an fcntl subright, but no CAP_FCNTL set EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, CAP_FCNTL_GETFL, 0, NULL, 0)); EXPECT_EQ(EINVAL, errno); // - Specify an ioctl subright, but no CAP_IOCTL set unsigned int ioctl1 = 1; EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, 1, &ioctl1, 0)); EXPECT_EQ(EINVAL, errno); // - N ioctls, but null pointer passed EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, 1, NULL, 0)); EXPECT_EQ(EINVAL, errno); // - Invalid nioctls EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, -2, NULL, 0)); EXPECT_EQ(EINVAL, errno); // - Null primary rights EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, NULL, 0, 0, NULL, 0)); EXPECT_EQ(EFAULT, errno); // - Invalid index bitmask rights.cr_rights[0] |= 3ULL << 57; EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, 0, NULL, 0)); EXPECT_EQ(EINVAL, errno); // - Invalid version rights.cr_rights[0] |= 2ULL << 62; EXPECT_EQ(-1, syscall(__NR_cap_rights_limit, fd, &rights, 0, 0, NULL, 0)); EXPECT_EQ(EINVAL, errno); close(fd); unlink(TmpFile("cap_invalid_rights")); } -FORK_TEST_ON(Linux, OpenByHandleAt, TmpFile("cap_openbyhandle_testfile")) { - REQUIRE_ROOT(); +FORK_TEST_ON(Linux, OpenByHandleAtIfRoot, TmpFile("cap_openbyhandle_testfile")) { + GTEST_SKIP_IF_NOT_ROOT(); int dir = open(tmpdir.c_str(), O_RDONLY); EXPECT_OK(dir); int fd = openat(dir, "cap_openbyhandle_testfile", O_RDWR|O_CREAT, 0644); EXPECT_OK(fd); const char* message = "Saved text"; EXPECT_OK(write(fd, message, strlen(message))); close(fd); struct file_handle* fhandle = (struct file_handle*)malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); fhandle->handle_bytes = MAX_HANDLE_SZ; int mount_id; EXPECT_OK(name_to_handle_at(dir, "cap_openbyhandle_testfile", fhandle, &mount_id, 0)); fd = open_by_handle_at(dir, fhandle, O_RDONLY); EXPECT_OK(fd); char buffer[200]; - EXPECT_OK(read(fd, buffer, 199)); - EXPECT_EQ(std::string(message), std::string(buffer)); + ssize_t len = read(fd, buffer, 199); + EXPECT_OK(len); + EXPECT_EQ(std::string(message), std::string(buffer, len)); close(fd); // Cannot issue open_by_handle_at after entering capability mode. cap_enter(); EXPECT_CAPMODE(open_by_handle_at(dir, fhandle, O_RDONLY)); close(dir); } int getrandom_(void *buf, size_t buflen, unsigned int flags) { #ifdef __NR_getrandom return syscall(__NR_getrandom, buf, buflen, flags); #else errno = ENOSYS; return -1; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) #include // Requires 3.17 kernel FORK_TEST(Linux, GetRandom) { EXPECT_OK(cap_enter()); unsigned char buffer[1024]; unsigned char buffer2[1024]; EXPECT_OK(getrandom_(buffer, sizeof(buffer), GRND_NONBLOCK)); EXPECT_OK(getrandom_(buffer2, sizeof(buffer2), GRND_NONBLOCK)); EXPECT_NE(0, memcmp(buffer, buffer2, sizeof(buffer))); } #endif int memfd_create_(const char *name, unsigned int flags) { #ifdef __NR_memfd_create return syscall(__NR_memfd_create, name, flags); #else errno = ENOSYS; return -1; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) #include // Requires 3.17 kernel -TEST(Linux, MemFDDeathTest) { +TEST(Linux, MemFDDeathTestIfAvailable) { int memfd = memfd_create_("capsicum-test", MFD_ALLOW_SEALING); if (memfd == -1 && errno == ENOSYS) { - TEST_SKIPPED("memfd_create(2) gives -ENOSYS"); - return; + GTEST_SKIP() << "memfd_create(2) gives -ENOSYS"; } const int LEN = 16; EXPECT_OK(ftruncate(memfd, LEN)); int memfd_ro = dup(memfd); int memfd_rw = dup(memfd); EXPECT_OK(memfd_ro); EXPECT_OK(memfd_rw); cap_rights_t rights; EXPECT_OK(cap_rights_limit(memfd_ro, cap_rights_init(&rights, CAP_MMAP_R, CAP_FSTAT))); EXPECT_OK(cap_rights_limit(memfd_rw, cap_rights_init(&rights, CAP_MMAP_RW, CAP_FCHMOD))); unsigned char *p_ro = (unsigned char *)mmap(NULL, LEN, PROT_READ, MAP_SHARED, memfd_ro, 0); EXPECT_NE((unsigned char *)MAP_FAILED, p_ro); unsigned char *p_rw = (unsigned char *)mmap(NULL, LEN, PROT_READ|PROT_WRITE, MAP_SHARED, memfd_rw, 0); EXPECT_NE((unsigned char *)MAP_FAILED, p_rw); EXPECT_EQ(MAP_FAILED, mmap(NULL, LEN, PROT_READ|PROT_WRITE, MAP_SHARED, memfd_ro, 0)); *p_rw = 42; EXPECT_EQ(42, *p_ro); EXPECT_DEATH(*p_ro = 42, ""); #ifndef F_ADD_SEALS // Hack for when libc6 does not yet include the updated linux/fcntl.h from kernel 3.17 #define _F_LINUX_SPECIFIC_BASE F_SETLEASE #define F_ADD_SEALS (_F_LINUX_SPECIFIC_BASE + 9) #define F_GET_SEALS (_F_LINUX_SPECIFIC_BASE + 10) #define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ #define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ #define F_SEAL_GROW 0x0004 /* prevent file from growing */ #define F_SEAL_WRITE 0x0008 /* prevent writes */ #endif // Reading the seal information requires CAP_FSTAT. int seals = fcntl(memfd, F_GET_SEALS); EXPECT_OK(seals); if (verbose) fprintf(stderr, "seals are %08x on base fd\n", seals); int seals_ro = fcntl(memfd_ro, F_GET_SEALS); EXPECT_EQ(seals, seals_ro); if (verbose) fprintf(stderr, "seals are %08x on read-only fd\n", seals_ro); int seals_rw = fcntl(memfd_rw, F_GET_SEALS); EXPECT_NOTCAPABLE(seals_rw); // Fail to seal as a writable mapping exists. EXPECT_EQ(-1, fcntl(memfd_rw, F_ADD_SEALS, F_SEAL_WRITE)); EXPECT_EQ(EBUSY, errno); *p_rw = 42; // Seal the rw version; need to unmap first. munmap(p_rw, LEN); munmap(p_ro, LEN); EXPECT_OK(fcntl(memfd_rw, F_ADD_SEALS, F_SEAL_WRITE)); seals = fcntl(memfd, F_GET_SEALS); EXPECT_OK(seals); if (verbose) fprintf(stderr, "seals are %08x on base fd\n", seals); seals_ro = fcntl(memfd_ro, F_GET_SEALS); EXPECT_EQ(seals, seals_ro); if (verbose) fprintf(stderr, "seals are %08x on read-only fd\n", seals_ro); // Remove the CAP_FCHMOD right, can no longer add seals. EXPECT_OK(cap_rights_limit(memfd_rw, cap_rights_init(&rights, CAP_MMAP_RW))); EXPECT_NOTCAPABLE(fcntl(memfd_rw, F_ADD_SEALS, F_SEAL_WRITE)); close(memfd); close(memfd_ro); close(memfd_rw); } #endif #else void noop() {} #endif diff --git a/contrib/capsicum-test/makefile b/contrib/capsicum-test/makefile index c92caeb3bc10..7b95e1927927 100644 --- a/contrib/capsicum-test/makefile +++ b/contrib/capsicum-test/makefile @@ -1,36 +1,36 @@ all: capsicum-test smoketest mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS) OBJECTS=capsicum-test-main.o capsicum-test.o capability-fd.o fexecve.o procdesc.o capmode.o fcntl.o ioctl.o openat.o sysctl.o select.o mqueue.o socket.o sctp.o capability-fd-pair.o linux.o overhead.o rename.o -GTEST_DIR=gtest-1.8.1 +GTEST_DIR=gtest-1.10.0 GTEST_INCS=-I$(GTEST_DIR)/include -I$(GTEST_DIR) GTEST_FLAGS=-DGTEST_USE_OWN_TR1_TUPLE=1 -DGTEST_HAS_TR1_TUPLE=1 CXXFLAGS+=$(ARCHFLAG) -Wall -g $(GTEST_INCS) $(GTEST_FLAGS) --std=c++11 CFLAGS+=$(ARCHFLAG) -Wall -g capsicum-test: $(OBJECTS) libgtest.a $(LOCAL_LIBS) $(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS) # Small statically-linked program for fexecve tests # (needs to be statically linked so that execve()ing it # doesn't involve ld.so traversing the filesystem). mini-me: mini-me.c $(CC) $(CFLAGS) -static -o $@ $< mini-me.noexec: mini-me cp mini-me $@ && chmod -x $@ mini-me.setuid: mini-me rm -f $@ && cp mini-me $@&& sudo chown root $@ && sudo chmod u+s $@ # Simple C test of Capsicum syscalls SMOKETEST_OBJECTS=smoketest.o smoketest: $(SMOKETEST_OBJECTS) $(LOCAL_LIBS) $(CC) $(CFLAGS) -o $@ $(SMOKETEST_OBJECTS) $(LIBCAPRIGHTS) test: capsicum-test mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS) ./capsicum-test gtest-all.o: - $(CXX) $(ARCHFLAG) -I$(GTEST_DIR)/include -I$(GTEST_DIR) $(GTEST_FLAGS) -c ${GTEST_DIR}/src/gtest-all.cc + $(CXX) $(CXXFLAGS) $(ARCHFLAG) -I$(GTEST_DIR)/include -I$(GTEST_DIR) $(GTEST_FLAGS) -c ${GTEST_DIR}/src/gtest-all.cc libgtest.a: gtest-all.o $(AR) -rv libgtest.a gtest-all.o clean: rm -rf gtest-all.o libgtest.a capsicum-test mini-me mini-me.noexec smoketest $(SMOKETEST_OBJECTS) $(OBJECTS) $(LOCAL_CLEAN) $(EXTRA_PROGS) diff --git a/contrib/capsicum-test/mqueue.cc b/contrib/capsicum-test/mqueue.cc index 42478c760020..f2f3daeb1db8 100644 --- a/contrib/capsicum-test/mqueue.cc +++ b/contrib/capsicum-test/mqueue.cc @@ -1,100 +1,99 @@ // Tests for POSIX message queue functionality. #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" // Run a test case in a forked process, possibly cleaning up a // message after completion #define FORK_TEST_ON_MQ(test_case_name, test_name, test_mq) \ 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 *mqname = test_mq; \ if (mqname) mq_unlink_(mqname); \ } \ static void test_case_name##_##test_name##_ForkTest() static bool invoked; void seen_it_done_it(int) { invoked = true; } -FORK_TEST_ON_MQ(PosixMqueue, CapMode, "/cap_mq") { +FORK_TEST_ON_MQ(PosixMqueue, CapModeIfMqOpenAvailable, "/cap_mq") { int mq = mq_open_("/cap_mq", O_RDWR|O_CREAT, 0644, NULL); // On FreeBSD, turn on message queue support with: // - 'kldload mqueuefs' // - 'options P1003_1B_MQUEUE' in kernel build config. if (mq < 0 && errno == ENOSYS) { - TEST_SKIPPED("mq_open -> -ENOSYS"); - return; + GTEST_SKIP() << "mq_open -> -ENOSYS"; } EXPECT_OK(mq); cap_rights_t r_read; cap_rights_init(&r_read, CAP_READ); cap_rights_t r_write; cap_rights_init(&r_write, CAP_WRITE); cap_rights_t r_poll; cap_rights_init(&r_poll, CAP_EVENT); int cap_read_mq = dup(mq); EXPECT_OK(cap_read_mq); EXPECT_OK(cap_rights_limit(cap_read_mq, &r_read)); int cap_write_mq = dup(mq); EXPECT_OK(cap_write_mq); EXPECT_OK(cap_rights_limit(cap_write_mq, &r_write)); int cap_poll_mq = dup(mq); EXPECT_OK(cap_poll_mq); EXPECT_OK(cap_rights_limit(cap_poll_mq, &r_poll)); EXPECT_OK(mq_close_(mq)); signal(SIGUSR2, seen_it_done_it); EXPECT_OK(cap_enter()); // Enter capability mode // Can no longer access the message queue via the POSIX IPC namespace. EXPECT_CAPMODE(mq_open_("/cap_mw", O_RDWR|O_CREAT, 0644, NULL)); struct sigevent se; se.sigev_notify = SIGEV_SIGNAL; se.sigev_signo = SIGUSR2; EXPECT_OK(mq_notify_(cap_poll_mq, &se)); EXPECT_NOTCAPABLE(mq_notify_(cap_read_mq, &se)); EXPECT_NOTCAPABLE(mq_notify_(cap_write_mq, &se)); const unsigned int kPriority = 10; const char* message = "xyzzy"; struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 0; EXPECT_OK(mq_timedsend_(cap_write_mq, message, strlen(message) + 1, kPriority, &ts)); EXPECT_NOTCAPABLE(mq_timedsend_(cap_read_mq, message, strlen(message) + 1, kPriority, &ts)); sleep(1); // Give the notification a chance to arrive. EXPECT_TRUE(invoked); struct mq_attr mqa; EXPECT_OK(mq_getattr_(cap_poll_mq, &mqa)); EXPECT_OK(mq_setattr_(cap_poll_mq, &mqa, NULL)); EXPECT_NOTCAPABLE(mq_getattr_(cap_write_mq, &mqa)); char* buffer = (char *)malloc(mqa.mq_msgsize); unsigned int priority; EXPECT_NOTCAPABLE(mq_timedreceive_(cap_write_mq, buffer, mqa.mq_msgsize, &priority, &ts)); EXPECT_OK(mq_timedreceive_(cap_read_mq, buffer, mqa.mq_msgsize, &priority, &ts)); EXPECT_EQ(std::string(message), std::string(buffer)); EXPECT_EQ(kPriority, priority); free(buffer); close(cap_read_mq); close(cap_write_mq); close(cap_poll_mq); } diff --git a/contrib/capsicum-test/procdesc.cc b/contrib/capsicum-test/procdesc.cc index 94c0dc5d774d..11274ce9e866 100644 --- a/contrib/capsicum-test/procdesc.cc +++ b/contrib/capsicum-test/procdesc.cc @@ -1,977 +1,980 @@ // Tests for the process descriptor API for Linux. #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" #ifndef __WALL // Linux requires __WALL in order for waitpid(specific_pid,...) to // see and reap any specific pid. Define this to nothing for platforms // (FreeBSD) where it doesn't exist, to reduce macroing. #define __WALL 0 #endif // TODO(drysdale): it would be nice to use proper synchronization between // processes, rather than synchronization-via-sleep; faster too. //------------------------------------------------ // Utilities for the tests. static pid_t pdwait4_(int pd, int *status, int options, struct rusage *ru) { #ifdef HAVE_PDWAIT4 return pdwait4(pd, status, options, ru); #else // Simulate pdwait4() with wait4(pdgetpid()); this won't work in capability mode. pid_t pid = -1; int rc = pdgetpid(pd, &pid); if (rc < 0) { return rc; } options |= __WALL; return wait4(pid, status, options, ru); #endif } static void print_rusage(FILE *f, struct rusage *ru) { fprintf(f, " User CPU time=%ld.%06ld\n", (long)ru->ru_utime.tv_sec, (long)ru->ru_utime.tv_usec); fprintf(f, " System CPU time=%ld.%06ld\n", (long)ru->ru_stime.tv_sec, (long)ru->ru_stime.tv_usec); fprintf(f, " Max RSS=%ld\n", ru->ru_maxrss); } static void print_stat(FILE *f, const struct stat *stat) { fprintf(f, "{ .st_dev=%ld, st_ino=%ld, st_mode=%04o, st_nlink=%ld, st_uid=%d, st_gid=%d,\n" " .st_rdev=%ld, .st_size=%ld, st_blksize=%ld, .st_block=%ld,\n " #ifdef HAVE_STAT_BIRTHTIME ".st_birthtime=%ld, " #endif ".st_atime=%ld, .st_mtime=%ld, .st_ctime=%ld}\n", (long)stat->st_dev, (long)stat->st_ino, stat->st_mode, (long)stat->st_nlink, stat->st_uid, stat->st_gid, (long)stat->st_rdev, (long)stat->st_size, (long)stat->st_blksize, (long)stat->st_blocks, #ifdef HAVE_STAT_BIRTHTIME (long)stat->st_birthtime, #endif (long)stat->st_atime, (long)stat->st_mtime, (long)stat->st_ctime); } static std::map had_signal; static void handle_signal(int x) { had_signal[x] = true; } // Check that the given child process terminates as expected. void CheckChildFinished(pid_t pid, bool signaled=false) { // Wait for the child to finish. int rc; int status = 0; do { rc = waitpid(pid, &status, __WALL); if (rc < 0) { fprintf(stderr, "Warning: waitpid error %s (%d)\n", strerror(errno), errno); ADD_FAILURE() << "Failed to wait for child"; break; } else if (rc == pid) { break; } } while (true); EXPECT_EQ(pid, rc); if (rc == pid) { if (signaled) { EXPECT_TRUE(WIFSIGNALED(status)); } else { EXPECT_TRUE(WIFEXITED(status)) << std::hex << status; EXPECT_EQ(0, WEXITSTATUS(status)); } } } //------------------------------------------------ // Basic tests of process descriptor functionality TEST(Pdfork, Simple) { int pd = -1; pid_t parent = getpid_(); int pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: check pid values. EXPECT_EQ(-1, pd); EXPECT_NE(parent, getpid_()); EXPECT_EQ(parent, getppid()); sleep(1); exit(0); } usleep(100); // ensure the child has a chance to run EXPECT_NE(-1, pd); EXPECT_PID_ALIVE(pid); int pid_got; EXPECT_OK(pdgetpid(pd, &pid_got)); EXPECT_EQ(pid, pid_got); // Wait long enough for the child to exit(). sleep(2); EXPECT_PID_ZOMBIE(pid); // Wait for the the child. int status; struct rusage ru; memset(&ru, 0, sizeof(ru)); int waitrc = pdwait4_(pd, &status, 0, &ru); EXPECT_EQ(pid, waitrc); if (verbose) { fprintf(stderr, "For pd %d pid %d:\n", pd, pid); print_rusage(stderr, &ru); } EXPECT_PID_GONE(pid); // Can only pdwait4(pd) once (as initial call reaps zombie). memset(&ru, 0, sizeof(ru)); EXPECT_EQ(-1, pdwait4_(pd, &status, 0, &ru)); EXPECT_EQ(ECHILD, errno); EXPECT_OK(close(pd)); } TEST(Pdfork, InvalidFlag) { int pd = -1; int pid = pdfork(&pd, PD_DAEMON<<5); if (pid == 0) { exit(1); } EXPECT_EQ(-1, pid); EXPECT_EQ(EINVAL, errno); if (pid > 0) waitpid(pid, NULL, __WALL); } TEST(Pdfork, TimeCheck) { time_t now = time(NULL); // seconds since epoch EXPECT_NE(-1, now); if (verbose) fprintf(stderr, "Calling pdfork around %ld\n", (long)(long)now); int pd = -1; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: check we didn't get a valid process descriptor then exit. EXPECT_EQ(-1, pdgetpid(pd, &pid)); EXPECT_EQ(EBADF, errno); exit(HasFailure()); } #ifdef HAVE_PROCDESC_FSTAT // Parent process. Ensure that [acm]times have been set correctly. struct stat stat; memset(&stat, 0, sizeof(stat)); EXPECT_OK(fstat(pd, &stat)); if (verbose) print_stat(stderr, &stat); #ifdef HAVE_STAT_BIRTHTIME EXPECT_GE(now, stat.st_birthtime); EXPECT_EQ(stat.st_birthtime, stat.st_atime); #endif EXPECT_LT((now - stat.st_atime), 2); EXPECT_EQ(stat.st_atime, stat.st_ctime); EXPECT_EQ(stat.st_ctime, stat.st_mtime); #endif // Wait for the child to finish. pid_t pd_pid = -1; EXPECT_OK(pdgetpid(pd, &pd_pid)); EXPECT_EQ(pid, pd_pid); CheckChildFinished(pid); } TEST(Pdfork, UseDescriptor) { int pd = -1; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: immediately exit exit(0); } CheckChildFinished(pid); } TEST(Pdfork, NonProcessDescriptor) { int fd = open("/etc/passwd", O_RDONLY); EXPECT_OK(fd); // pd*() operations should fail on a non-process descriptor. EXPECT_EQ(-1, pdkill(fd, SIGUSR1)); int status; EXPECT_EQ(-1, pdwait4_(fd, &status, 0, NULL)); pid_t pid; EXPECT_EQ(-1, pdgetpid(fd, &pid)); close(fd); } static void *SubThreadMain(void *) { while (true) { if (verbose) fprintf(stderr, " subthread: \"I aten't dead\"\n"); usleep(100000); } return NULL; } static void *ThreadMain(void *) { int pd; pid_t child = pdfork(&pd, 0); if (child == 0) { // Child: start a subthread then loop pthread_t child_subthread; EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain, NULL)); while (true) { if (verbose) fprintf(stderr, " pdforked process %d: \"I aten't dead\"\n", getpid()); usleep(100000); } exit(0); } if (verbose) fprintf(stderr, " thread generated pd %d\n", pd); sleep(2); // Pass the process descriptor back to the main thread. return reinterpret_cast(pd); } TEST(Pdfork, FromThread) { // Fire off a new thread to do all of the creation work. pthread_t child_thread; EXPECT_OK(pthread_create(&child_thread, NULL, ThreadMain, NULL)); void *data; EXPECT_OK(pthread_join(child_thread, &data)); int pd = reinterpret_cast(data); if (verbose) fprintf(stderr, "retrieved pd %d from terminated thread\n", pd); // Kill and reap. pid_t pid; EXPECT_OK(pdgetpid(pd, &pid)); EXPECT_OK(pdkill(pd, SIGKILL)); int status; EXPECT_EQ(pid, pdwait4_(pd, &status, 0, NULL)); EXPECT_TRUE(WIFSIGNALED(status)); } //------------------------------------------------ // More complicated tests. // Test fixture that pdfork()s off a child process, which terminates // when it receives anything on a pipe. class PipePdforkBase : public ::testing::Test { public: PipePdforkBase(int pdfork_flags) : pd_(-1), pid_(-1) { had_signal.clear(); int pipes[2]; EXPECT_OK(pipe(pipes)); pipe_ = pipes[1]; int parent = getpid_(); if (verbose) fprintf(stderr, "[%d] about to pdfork()\n", getpid_()); int rc = pdfork(&pd_, pdfork_flags); EXPECT_OK(rc); if (rc == 0) { // Child process: blocking-read an int from the pipe then exit with that value. EXPECT_NE(parent, getpid_()); EXPECT_EQ(parent, getppid()); if (verbose) fprintf(stderr, " [%d] child of %d waiting for value on pipe\n", getpid_(), getppid()); read(pipes[0], &rc, sizeof(rc)); if (verbose) fprintf(stderr, " [%d] got value %d on pipe, exiting\n", getpid_(), rc); exit(rc); } pid_ = rc; usleep(100); // ensure the child has a chance to run } ~PipePdforkBase() { // Terminate by any means necessary. if (pd_ > 0) { pdkill(pd_, SIGKILL); close(pd_); } if (pid_ > 0) { kill(pid_, SIGKILL); waitpid(pid_, NULL, __WALL|WNOHANG); } // Check signal expectations. EXPECT_FALSE(had_signal[SIGCHLD]); } int TerminateChild() { // Tell the child to exit. int zero = 0; if (verbose) fprintf(stderr, "[%d] write 0 to pipe\n", getpid_()); return write(pipe_, &zero, sizeof(zero)); } protected: int pd_; int pipe_; pid_t pid_; }; class PipePdfork : public PipePdforkBase { public: PipePdfork() : PipePdforkBase(0) {} }; class PipePdforkDaemon : public PipePdforkBase { public: PipePdforkDaemon() : PipePdforkBase(PD_DAEMON) {} }; // Can we poll a process descriptor? TEST_F(PipePdfork, Poll) { // Poll the process descriptor, nothing happening. struct pollfd fdp; fdp.fd = pd_; fdp.events = POLLIN | POLLERR | POLLHUP; fdp.revents = 0; EXPECT_EQ(0, poll(&fdp, 1, 0)); TerminateChild(); // Poll again, should have activity on the process descriptor. EXPECT_EQ(1, poll(&fdp, 1, 2000)); EXPECT_TRUE(fdp.revents & POLLHUP); // Poll a third time, still have POLLHUP. fdp.revents = 0; EXPECT_EQ(1, poll(&fdp, 1, 0)); EXPECT_TRUE(fdp.revents & POLLHUP); } // Can multiple processes poll on the same descriptor? TEST_F(PipePdfork, PollMultiple) { int child = fork(); EXPECT_OK(child); if (child == 0) { // Child: wait to give time for setup, then write to the pipe (which will // induce exit of the pdfork()ed process) and exit. sleep(1); TerminateChild(); exit(0); } usleep(100); // ensure the child has a chance to run // Fork again int doppel = fork(); EXPECT_OK(doppel); // We now have: // pid A: main process, here // |--pid B: pdfork()ed process, blocked on read() // |--pid C: fork()ed process, in sleep(1) above // +--pid D: doppel process, here // Both A and D execute the following code. // First, check no activity on the process descriptor yet. struct pollfd fdp; fdp.fd = pd_; fdp.events = POLLIN | POLLERR | POLLHUP; fdp.revents = 0; EXPECT_EQ(0, poll(&fdp, 1, 0)); // Now, wait (indefinitely) for activity on the process descriptor. // We expect: // - pid C will finish its sleep, write to the pipe and exit // - pid B will unblock from read(), and exit // - this will generate an event on the process descriptor... // - ...in both process A and process D. EXPECT_EQ(1, poll(&fdp, 1, 2000)); EXPECT_TRUE(fdp.revents & POLLHUP); if (doppel == 0) { // Child: process D exits. exit(0); } else { // Parent: wait on process D. int rc = 0; waitpid(doppel, &rc, __WALL); EXPECT_TRUE(WIFEXITED(rc)); EXPECT_EQ(0, WEXITSTATUS(rc)); // Also wait on process B. CheckChildFinished(child); } } // Check that exit status/rusage for a dead pdfork()ed child can be retrieved // via any process descriptor, multiple times. TEST_F(PipePdfork, MultipleRetrieveExitStatus) { EXPECT_PID_ALIVE(pid_); int pd_copy = dup(pd_); EXPECT_LT(0, TerminateChild()); int status; struct rusage ru; memset(&ru, 0, sizeof(ru)); int waitrc = pdwait4_(pd_copy, &status, 0, &ru); EXPECT_EQ(pid_, waitrc); if (verbose) { fprintf(stderr, "For pd %d -> pid %d:\n", pd_, pid_); print_rusage(stderr, &ru); } EXPECT_PID_GONE(pid_); #ifdef NOTYET // Child has been reaped, so original process descriptor dangles but // still has access to rusage information. memset(&ru, 0, sizeof(ru)); EXPECT_EQ(0, pdwait4_(pd_, &status, 0, &ru)); #endif close(pd_copy); } TEST_F(PipePdfork, ChildExit) { EXPECT_PID_ALIVE(pid_); EXPECT_LT(0, TerminateChild()); EXPECT_PID_DEAD(pid_); int status; int rc = pdwait4_(pd_, &status, 0, NULL); EXPECT_OK(rc); EXPECT_EQ(pid_, rc); pid_ = 0; } #ifdef HAVE_PROC_FDINFO TEST_F(PipePdfork, FdInfo) { char buffer[1024]; sprintf(buffer, "/proc/%d/fdinfo/%d", getpid_(), pd_); int procfd = open(buffer, O_RDONLY); EXPECT_OK(procfd); EXPECT_OK(read(procfd, buffer, sizeof(buffer))); // The fdinfo should include the file pos of the underlying file EXPECT_NE((char*)NULL, strstr(buffer, "pos:\t0")) << buffer; // ...and the underlying pid char pidline[256]; sprintf(pidline, "pid:\t%d", pid_); EXPECT_NE((char*)NULL, strstr(buffer, pidline)) << buffer; close(procfd); } #endif // Closing a normal process descriptor terminates the underlying process. TEST_F(PipePdfork, Close) { sighandler_t original = signal(SIGCHLD, handle_signal); EXPECT_PID_ALIVE(pid_); int status; EXPECT_EQ(0, waitpid(pid_, &status, __WALL|WNOHANG)); EXPECT_OK(close(pd_)); pd_ = -1; EXPECT_FALSE(had_signal[SIGCHLD]); EXPECT_PID_DEAD(pid_); #ifdef __FreeBSD__ EXPECT_EQ(-1, waitpid(pid_, NULL, __WALL)); EXPECT_EQ(errno, ECHILD); #else // Having closed the process descriptor means that pdwait4(pd) now doesn't work. int rc = pdwait4_(pd_, &status, 0, NULL); EXPECT_EQ(-1, rc); EXPECT_EQ(EBADF, errno); // Closing all process descriptors means the the child can only be reaped via pid. EXPECT_EQ(pid_, waitpid(pid_, &status, __WALL|WNOHANG)); #endif signal(SIGCHLD, original); } TEST_F(PipePdfork, CloseLast) { sighandler_t original = signal(SIGCHLD, handle_signal); // Child should only die when last process descriptor is closed. EXPECT_PID_ALIVE(pid_); int pd_other = dup(pd_); EXPECT_OK(close(pd_)); pd_ = -1; EXPECT_PID_ALIVE(pid_); int status; EXPECT_EQ(0, waitpid(pid_, &status, __WALL|WNOHANG)); // Can no longer pdwait4() the closed process descriptor... EXPECT_EQ(-1, pdwait4_(pd_, &status, WNOHANG, NULL)); EXPECT_EQ(EBADF, errno); // ...but can pdwait4() the still-open process descriptor. errno = 0; EXPECT_EQ(0, pdwait4_(pd_other, &status, WNOHANG, NULL)); EXPECT_EQ(0, errno); EXPECT_OK(close(pd_other)); EXPECT_PID_DEAD(pid_); EXPECT_FALSE(had_signal[SIGCHLD]); signal(SIGCHLD, original); } -FORK_TEST(Pdfork, OtherUser) { - REQUIRE_ROOT(); +FORK_TEST(Pdfork, OtherUserIfRoot) { + GTEST_SKIP_IF_NOT_ROOT(); int pd; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child process: loop forever. while (true) usleep(100000); } usleep(100); // Now that the second process has been pdfork()ed, change euid. - setuid(other_uid); + ASSERT_NE(0u, other_uid) << "other_uid not initialized correctly, " + "please pass the -u flag."; + EXPECT_EQ(0, setuid(other_uid)); + EXPECT_EQ(other_uid, getuid()); if (verbose) fprintf(stderr, "uid=%d euid=%d\n", getuid(), geteuid()); // Fail to kill child with normal PID operation. EXPECT_EQ(-1, kill(pid, SIGKILL)); EXPECT_EQ(EPERM, errno); EXPECT_PID_ALIVE(pid); // Succeed with pdkill though. EXPECT_OK(pdkill(pd, SIGKILL)); EXPECT_PID_ZOMBIE(pid); int status; int rc = pdwait4_(pd, &status, WNOHANG, NULL); EXPECT_OK(rc); EXPECT_EQ(pid, rc); EXPECT_TRUE(WIFSIGNALED(status)); } TEST_F(PipePdfork, WaitPidThenPd) { TerminateChild(); int status; // If we waitpid(pid) first... int rc = waitpid(pid_, &status, __WALL); EXPECT_OK(rc); EXPECT_EQ(pid_, rc); #ifdef NOTYET // ...the zombie is reaped but we can still subsequently pdwait4(pd). EXPECT_EQ(0, pdwait4_(pd_, &status, 0, NULL)); #endif } TEST_F(PipePdfork, WaitPdThenPid) { TerminateChild(); int status; // If we pdwait4(pd) first... int rc = pdwait4_(pd_, &status, 0, NULL); EXPECT_OK(rc); EXPECT_EQ(pid_, rc); // ...the zombie is reaped and cannot subsequently waitpid(pid). EXPECT_EQ(-1, waitpid(pid_, &status, __WALL)); EXPECT_EQ(ECHILD, errno); } // Setting PD_DAEMON prevents close() from killing the child. TEST_F(PipePdforkDaemon, Close) { EXPECT_OK(close(pd_)); pd_ = -1; EXPECT_PID_ALIVE(pid_); // Can still explicitly kill it via the pid. if (pid_ > 0) { EXPECT_OK(kill(pid_, SIGKILL)); EXPECT_PID_DEAD(pid_); } } static void TestPdkill(pid_t pid, int pd) { EXPECT_PID_ALIVE(pid); // SIGCONT is ignored by default. EXPECT_OK(pdkill(pd, SIGCONT)); EXPECT_PID_ALIVE(pid); // SIGINT isn't EXPECT_OK(pdkill(pd, SIGINT)); EXPECT_PID_DEAD(pid); // pdkill() on zombie is no-op. errno = 0; EXPECT_EQ(0, pdkill(pd, SIGINT)); EXPECT_EQ(0, errno); // pdkill() on reaped process gives -ESRCH. CheckChildFinished(pid, true); EXPECT_EQ(-1, pdkill(pd, SIGINT)); EXPECT_EQ(ESRCH, errno); } TEST_F(PipePdfork, Pdkill) { TestPdkill(pid_, pd_); } TEST_F(PipePdforkDaemon, Pdkill) { TestPdkill(pid_, pd_); } TEST(Pdfork, PdkillOtherSignal) { int pd = -1; int pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: watch for SIGUSR1 forever. had_signal.clear(); signal(SIGUSR1, handle_signal); while (!had_signal[SIGUSR1]) { usleep(100000); } exit(123); } sleep(1); // Send an invalid signal. EXPECT_EQ(-1, pdkill(pd, 0xFFFF)); EXPECT_EQ(EINVAL, errno); // Send an expected SIGUSR1 to the pdfork()ed child. EXPECT_PID_ALIVE(pid); pdkill(pd, SIGUSR1); EXPECT_PID_DEAD(pid); // Child's exit status confirms whether it received the signal. int status; int rc = waitpid(pid, &status, __WALL); EXPECT_OK(rc); EXPECT_EQ(pid, rc); EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << rc; EXPECT_EQ(123, WEXITSTATUS(status)); } pid_t PdforkParentDeath(int pdfork_flags) { // Set up: // pid A: main process, here // +--pid B: fork()ed process, sleep(4)s then exits // +--pid C: pdfork()ed process, looping forever int sock_fds[2]; EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds)); if (verbose) fprintf(stderr, "[%d] parent about to fork()...\n", getpid_()); pid_t child = fork(); EXPECT_OK(child); if (child == 0) { int pd; if (verbose) fprintf(stderr, " [%d] child about to pdfork()...\n", getpid_()); pid_t grandchild = pdfork(&pd, pdfork_flags); if (grandchild == 0) { while (true) { if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", getpid_()); sleep(1); } } if (verbose) fprintf(stderr, " [%d] pdfork()ed grandchild %d, sending ID to parent\n", getpid_(), grandchild); // send grandchild pid to parent write(sock_fds[1], &grandchild, sizeof(grandchild)); sleep(4); if (verbose) fprintf(stderr, " [%d] child terminating\n", getpid_()); exit(0); } if (verbose) fprintf(stderr, "[%d] fork()ed child is %d\n", getpid_(), child); pid_t grandchild; read(sock_fds[0], &grandchild, sizeof(grandchild)); if (verbose) fprintf(stderr, "[%d] receive grandchild id %d\n", getpid_(), grandchild); EXPECT_PID_ALIVE(child); EXPECT_PID_ALIVE(grandchild); sleep(6); // Child dies, closing its process descriptor for the grandchild. EXPECT_PID_DEAD(child); CheckChildFinished(child); return grandchild; } TEST(Pdfork, Bagpuss) { // "And of course when Bagpuss goes to sleep, all his friends go to sleep too" pid_t grandchild = PdforkParentDeath(0); // By default: child death => closed process descriptor => grandchild death. EXPECT_PID_DEAD(grandchild); } TEST(Pdfork, BagpussDaemon) { pid_t grandchild = PdforkParentDeath(PD_DAEMON); // With PD_DAEMON: child death => closed process descriptor => no effect on grandchild. EXPECT_PID_ALIVE(grandchild); if (grandchild > 0) { EXPECT_OK(kill(grandchild, SIGKILL)); } } // The exit of a pdfork()ed process should not generate SIGCHLD. TEST_F(PipePdfork, NoSigchld) { had_signal.clear(); sighandler_t original = signal(SIGCHLD, handle_signal); TerminateChild(); int rc = 0; // Can waitpid() for the specific pid of the pdfork()ed child. EXPECT_EQ(pid_, waitpid(pid_, &rc, __WALL)); EXPECT_TRUE(WIFEXITED(rc)) << "0x" << std::hex << rc; EXPECT_FALSE(had_signal[SIGCHLD]); signal(SIGCHLD, original); } // The exit of a pdfork()ed process whose process descriptors have // all been closed should generate SIGCHLD. The child process needs // PD_DAEMON to survive the closure of the process descriptors. TEST_F(PipePdforkDaemon, NoPDSigchld) { had_signal.clear(); sighandler_t original = signal(SIGCHLD, handle_signal); EXPECT_OK(close(pd_)); TerminateChild(); #ifdef __FreeBSD__ EXPECT_EQ(-1, waitpid(pid_, NULL, __WALL)); EXPECT_EQ(errno, ECHILD); #else int rc = 0; // Can waitpid() for the specific pid of the pdfork()ed child. EXPECT_EQ(pid_, waitpid(pid_, &rc, __WALL)); EXPECT_TRUE(WIFEXITED(rc)) << "0x" << std::hex << rc; #endif EXPECT_FALSE(had_signal[SIGCHLD]); signal(SIGCHLD, original); } #ifdef HAVE_PROCDESC_FSTAT TEST_F(PipePdfork, ModeBits) { // Owner rwx bits indicate liveness of child struct stat stat; memset(&stat, 0, sizeof(stat)); EXPECT_OK(fstat(pd_, &stat)); if (verbose) print_stat(stderr, &stat); EXPECT_EQ(S_IRWXU, (long)(stat.st_mode & S_IRWXU)); TerminateChild(); usleep(100000); memset(&stat, 0, sizeof(stat)); EXPECT_OK(fstat(pd_, &stat)); if (verbose) print_stat(stderr, &stat); EXPECT_EQ(0, (int)(stat.st_mode & S_IRWXU)); } #endif TEST_F(PipePdfork, WildcardWait) { // TODO(FreeBSD): make wildcard wait ignore pdfork()ed children // https://bugs.freebsd.org/201054 TerminateChild(); sleep(1); // Ensure child is truly dead. // Wildcard waitpid(-1) should not see the pdfork()ed child because // there is still a process descriptor for it. int rc; EXPECT_EQ(-1, waitpid(-1, &rc, WNOHANG)); EXPECT_EQ(ECHILD, errno); EXPECT_OK(close(pd_)); pd_ = -1; } FORK_TEST(Pdfork, Pdkill) { had_signal.clear(); int pd; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: set a SIGINT handler and sleep. had_signal.clear(); signal(SIGINT, handle_signal); if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_()); int left = sleep(10); if (verbose) fprintf(stderr, "[%d] child slept, %d sec left, had[SIGINT]=%d\n", getpid_(), left, had_signal[SIGINT]); // Expect this sleep to be interrupted by the signal (and so left > 0). exit(left == 0); } // Parent: get child's PID. pid_t pd_pid; EXPECT_OK(pdgetpid(pd, &pd_pid)); EXPECT_EQ(pid, pd_pid); // Interrupt the child after a second. sleep(1); EXPECT_OK(pdkill(pd, SIGINT)); // Make sure the child finished properly (caught signal then exited). CheckChildFinished(pid); } FORK_TEST(Pdfork, PdkillSignal) { int pd; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: sleep. No SIGINT handler. if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_()); int left = sleep(10); if (verbose) fprintf(stderr, "[%d] child slept, %d sec left\n", getpid_(), left); exit(99); } // Kill the child (as it doesn't handle SIGINT). sleep(1); EXPECT_OK(pdkill(pd, SIGINT)); // Make sure the child finished properly (terminated by signal). CheckChildFinished(pid, true); } //------------------------------------------------ // Test interactions with other parts of Capsicum: // - capability mode // - capabilities FORK_TEST(Pdfork, DaemonUnrestricted) { EXPECT_OK(cap_enter()); int fd; // Capability mode leaves pdfork() available, with and without flag. int rc; rc = pdfork(&fd, PD_DAEMON); EXPECT_OK(rc); if (rc == 0) { // Child: immediately terminate. exit(0); } rc = pdfork(&fd, 0); EXPECT_OK(rc); if (rc == 0) { // Child: immediately terminate. exit(0); } } TEST(Pdfork, MissingRights) { pid_t parent = getpid_(); int pd = -1; pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { // Child: loop forever. EXPECT_NE(parent, getpid_()); while (true) sleep(1); } // Create two capabilities from the process descriptor. cap_rights_t r_ro; cap_rights_init(&r_ro, CAP_READ, CAP_LOOKUP); int cap_incapable = dup(pd); EXPECT_OK(cap_incapable); EXPECT_OK(cap_rights_limit(cap_incapable, &r_ro)); cap_rights_t r_pdall; cap_rights_init(&r_pdall, CAP_PDGETPID, CAP_PDWAIT, CAP_PDKILL); int cap_capable = dup(pd); EXPECT_OK(cap_capable); EXPECT_OK(cap_rights_limit(cap_capable, &r_pdall)); pid_t other_pid; EXPECT_NOTCAPABLE(pdgetpid(cap_incapable, &other_pid)); EXPECT_NOTCAPABLE(pdkill(cap_incapable, SIGINT)); int status; EXPECT_NOTCAPABLE(pdwait4_(cap_incapable, &status, 0, NULL)); EXPECT_OK(pdgetpid(cap_capable, &other_pid)); EXPECT_EQ(pid, other_pid); EXPECT_OK(pdkill(cap_capable, SIGINT)); int rc = pdwait4_(pd, &status, 0, NULL); EXPECT_OK(rc); EXPECT_EQ(pid, rc); } //------------------------------------------------ // Passing process descriptors between processes. TEST_F(PipePdfork, PassProcessDescriptor) { 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; if (verbose) fprintf(stderr, "[%d] about to fork()\n", getpid_()); pid_t child2 = fork(); if (child2 == 0) { // Child: close our copy of the original process descriptor. close(pd_); // Child: wait to receive process descriptor over socket if (verbose) fprintf(stderr, " [%d] child of %d waiting for process descriptor on socket\n", getpid_(), getppid()); 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 pd = *(int*)CMSG_DATA(cmptr); EXPECT_EQ(CMSG_LEN(sizeof(int)), cmptr->cmsg_len); cmptr = CMSG_NXTHDR(&mh, cmptr); EXPECT_TRUE(cmptr == NULL); if (verbose) fprintf(stderr, " [%d] got process descriptor %d on socket\n", getpid_(), pd); // Child: confirm we can do pd*() operations on the process descriptor pid_t other; EXPECT_OK(pdgetpid(pd, &other)); if (verbose) fprintf(stderr, " [%d] process descriptor %d is pid %d\n", getpid_(), pd, other); sleep(2); if (verbose) fprintf(stderr, " [%d] close process descriptor %d\n", getpid_(), pd); close(pd); // Last process descriptor closed, expect death EXPECT_PID_DEAD(other); exit(HasFailure()); } usleep(1000); // Ensure subprocess runs // Send the process 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) = pd_; buffer1[0] = 0; iov[0].iov_len = 1; sleep(1); if (verbose) fprintf(stderr, "[%d] send process descriptor %d on socket\n", getpid_(), pd_); int rc = sendmsg(sock_fds[1], &mh, 0); EXPECT_OK(rc); if (verbose) fprintf(stderr, "[%d] close process descriptor %d\n", getpid_(), pd_); close(pd_); // Not last open process descriptor // wait for child2 int status; EXPECT_EQ(child2, waitpid(child2, &status, __WALL)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); // confirm death all round EXPECT_PID_DEAD(child2); EXPECT_PID_DEAD(pid_); }