diff --git a/GNUmakefile b/GNUmakefile index d7133ca3b386..426eb49cdfa1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,78 +1,82 @@ OS:=$(shell uname) # Set ARCH to 32 or x32 for i386/x32 ABIs ARCH?=64 ARCHFLAG=-m$(ARCH) +ifeq ($(OS),FreeBSD) +EXTRA_LIBS=-lprocstat +endif + ifeq ($(OS),Linux) PROCESSOR:=$(shell uname -p) ifneq ($(wildcard /usr/lib/$(PROCESSOR)-linux-gnu),) # Can use standard Debian location for static libraries. PLATFORM_LIBDIR=/usr/lib/$(PROCESSOR)-linux-gnu else # Attempt to determine library location from gcc configuration. PLATFORM_LIBDIR=$(shell gcc -v 2>&1 | grep "Configured with:" | sed 's/.*--libdir=\(\/usr\/[^ ]*\).*/\1/g') endif # Override for explicitly specified ARCHFLAG. # Use locally compiled libcaprights in this case, on the # assumption that any installed version is 64-bit. ifeq ($(ARCHFLAG),-m32) PROCESSOR=i386 PLATFORM_LIBDIR=/usr/lib32 LIBCAPRIGHTS=./libcaprights.a endif ifeq ($(ARCHFLAG),-mx32) PROCESSOR=x32 PLATFORM_LIBDIR=/usr/libx32 LIBCAPRIGHTS=./libcaprights.a endif # Detect presence of libsctp in normal Debian location ifneq ($(wildcard $(PLATFORM_LIBDIR)/libsctp.a),) LIBSCTP=-lsctp CXXFLAGS=-DHAVE_SCTP endif ifneq ($(LIBCAPRIGHTS),) # Build local libcaprights.a (assuming ./configure # has already been done in libcaprights/) LOCAL_LIBS=$(LIBCAPRIGHTS) LIBCAPRIGHTS_OBJS=libcaprights/capsicum.o libcaprights/linux-bpf-capmode.o libcaprights/procdesc.o libcaprights/signal.o LOCAL_CLEAN=$(LOCAL_LIBS) $(LIBCAPRIGHTS_OBJS) else # Detect installed libcaprights static library. ifneq ($(wildcard $(PLATFORM_LIBDIR)/libcaprights.a),) LIBCAPRIGHTS=$(PLATFORM_LIBDIR)/libcaprights.a else ifneq ($(wildcard /usr/lib/libcaprights.a),) LIBCAPRIGHTS=/usr/lib/libcaprights.a endif endif endif endif # Extra test programs for arch-transition tests EXTRA_PROGS = mini-me.32 mini-me.64 ifneq ($(wildcard /usr/include/gnu/stubs-x32.h),) EXTRA_PROGS += mini-me.x32 endif # Chain on to the master makefile include makefile ./libcaprights.a: $(LIBCAPRIGHTS_OBJS) ar cr $@ $^ # Small static programs of known architectures # These may require additional packages to be installed; for example, for Debian: # - libc6-dev-i386 provides 32-bit headers for a 64-bit system # - libc6-dev-x32 provides headers for the x32 ABI. mini-me.32: mini-me.c $(CC) $(CFLAGS) -m32 -static -o $@ $< mini-me.x32: mini-me.c $(CC) $(CFLAGS) -mx32 -static -o $@ $< mini-me.64: mini-me.c $(CC) $(CFLAGS) -m64 -static -o $@ $< diff --git a/capability-fd.cc b/capability-fd.cc index a454d54aa86a..f255c6425cdd 100644 --- a/capability-fd.cc +++ b/capability-fd.cc @@ -1,1335 +1,1344 @@ #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 { \ + SCOPED_TRACE(#__VA_ARGS__); \ 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: send startup notification + SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_STARTED); // 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: acknowledge that we have received and tested the file descriptor + SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_FD_RECEIVED); + // Child: wait for a normal read - int val; - read(sock_fds[0], &val, sizeof(val)); - exit(0); + AWAIT_INT_MESSAGE(sock_fds[0], MSG_PARENT_REQUEST_CHILD_EXIT); + exit(testing::Test::HasFailure()); } 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); + // Wait for child to start up: + AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_STARTED); + // 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)); + // Check that the child received the message + AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_FD_RECEIVED); + + // Tell the child to exit + SEND_INT_MESSAGE(sock_fds[1], MSG_PARENT_REQUEST_CHILD_EXIT); } 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_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_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 close(cap_dfd_all); 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")); } 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 close(fd); 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, 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 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/capmode.cc b/capmode.cc index 567773f319d9..c274f5e1c9f3 100644 --- a/capmode.cc +++ b/capmode.cc @@ -1,654 +1,730 @@ // Test routines to make sure a variety of system calls are or are not // available in capability mode. The goal is not to see if they work, just // whether or not they return the expected ECAPMODE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" // Test fixture that opens (and closes) a bunch of files. class WithFiles : public ::testing::Test { public: WithFiles() : fd_file_(open(TmpFile("cap_capmode"), O_RDWR|O_CREAT, 0644)), fd_close_(open("/dev/null", O_RDWR)), fd_dir_(open(tmpdir.c_str(), O_RDONLY)), fd_socket_(socket(PF_INET, SOCK_DGRAM, 0)), fd_tcp_socket_(socket(PF_INET, SOCK_STREAM, 0)) { EXPECT_OK(fd_file_); EXPECT_OK(fd_close_); EXPECT_OK(fd_dir_); EXPECT_OK(fd_socket_); EXPECT_OK(fd_tcp_socket_); } ~WithFiles() { if (fd_tcp_socket_ >= 0) close(fd_tcp_socket_); if (fd_socket_ >= 0) close(fd_socket_); if (fd_dir_ >= 0) close(fd_dir_); if (fd_close_ >= 0) close(fd_close_); if (fd_file_ >= 0) close(fd_file_); unlink(TmpFile("cap_capmode")); } protected: int fd_file_; int fd_close_; int fd_dir_; int fd_socket_; int fd_tcp_socket_; }; FORK_TEST_F(WithFiles, DisallowedFileSyscalls) { unsigned int mode = -1; EXPECT_OK(cap_getmode(&mode)); EXPECT_EQ(0, (int)mode); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(cap_getmode(&mode)); EXPECT_EQ(1, (int)mode); // System calls that are not permitted in capability mode. EXPECT_CAPMODE(access(TmpFile("cap_capmode_access"), F_OK)); EXPECT_CAPMODE(acct(TmpFile("cap_capmode_acct"))); EXPECT_CAPMODE(chdir(TmpFile("cap_capmode_chdir"))); #ifdef HAVE_CHFLAGS EXPECT_CAPMODE(chflags(TmpFile("cap_capmode_chflags"), UF_NODUMP)); #endif EXPECT_CAPMODE(chmod(TmpFile("cap_capmode_chmod"), 0644)); EXPECT_CAPMODE(chown(TmpFile("cap_capmode_chown"), -1, -1)); EXPECT_CAPMODE(chroot(TmpFile("cap_capmode_chroot"))); EXPECT_CAPMODE(creat(TmpFile("cap_capmode_creat"), 0644)); EXPECT_CAPMODE(fchdir(fd_dir_)); #ifdef HAVE_GETFSSTAT struct statfs statfs; EXPECT_CAPMODE(getfsstat(&statfs, sizeof(statfs), MNT_NOWAIT)); #endif EXPECT_CAPMODE(link(TmpFile("foo"), TmpFile("bar"))); struct stat sb; EXPECT_CAPMODE(lstat(TmpFile("cap_capmode_lstat"), &sb)); EXPECT_CAPMODE(mknod(TmpFile("capmode_mknod"), 0644 | S_IFIFO, 0)); EXPECT_CAPMODE(bogus_mount_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); char buf[64]; EXPECT_CAPMODE(readlink(TmpFile("cap_capmode_readlink"), buf, sizeof(buf))); #ifdef HAVE_REVOKE EXPECT_CAPMODE(revoke(TmpFile("cap_capmode_revoke"))); #endif EXPECT_CAPMODE(stat(TmpFile("cap_capmode_stat"), &sb)); EXPECT_CAPMODE(symlink(TmpFile("cap_capmode_symlink_from"), TmpFile("cap_capmode_symlink_to"))); EXPECT_CAPMODE(unlink(TmpFile("cap_capmode_unlink"))); EXPECT_CAPMODE(umount2("/not_mounted", 0)); } FORK_TEST_F(WithFiles, DisallowedSocketSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. // System calls that are not permitted in capability mode. struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = htonl(INADDR_ANY); EXPECT_CAPMODE(bind_(fd_socket_, (sockaddr*)&addr, sizeof(addr))); addr.sin_family = AF_INET; addr.sin_port = 53; addr.sin_addr.s_addr = htonl(0x08080808); EXPECT_CAPMODE(connect_(fd_tcp_socket_, (sockaddr*)&addr, sizeof(addr))); } FORK_TEST_F(WithFiles, AllowedFileSyscalls) { int rc; EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(close(fd_close_)); fd_close_ = -1; int fd_dup = dup(fd_file_); EXPECT_OK(fd_dup); EXPECT_OK(dup2(fd_file_, fd_dup)); #ifdef HAVE_DUP3 EXPECT_OK(dup3(fd_file_, fd_dup, 0)); #endif if (fd_dup >= 0) close(fd_dup); struct stat sb; EXPECT_OK(fstat(fd_file_, &sb)); EXPECT_OK(lseek(fd_file_, 0, SEEK_SET)); char ch; EXPECT_OK(read(fd_file_, &ch, sizeof(ch))); EXPECT_OK(write(fd_file_, &ch, sizeof(ch))); #ifdef HAVE_CHFLAGS rc = fchflags(fd_file_, UF_NODUMP); if (rc < 0) { EXPECT_NE(ECAPMODE, errno); } #endif char buf[1024]; rc = getdents_(fd_dir_, (void*)buf, sizeof(buf)); EXPECT_OK(rc); char data[] = "123"; EXPECT_OK(pwrite(fd_file_, data, 1, 0)); EXPECT_OK(pread(fd_file_, data, 1, 0)); struct iovec io; io.iov_base = data; io.iov_len = 2; #if !defined(__i386__) && !defined(__linux__) // TODO(drysdale): reinstate these tests for 32-bit runs when possible // libc bug is fixed. EXPECT_OK(pwritev(fd_file_, &io, 1, 0)); EXPECT_OK(preadv(fd_file_, &io, 1, 0)); #endif EXPECT_OK(writev(fd_file_, &io, 1)); EXPECT_OK(readv(fd_file_, &io, 1)); #ifdef HAVE_SYNCFS EXPECT_OK(syncfs(fd_file_)); #endif #ifdef HAVE_SYNC_FILE_RANGE EXPECT_OK(sync_file_range(fd_file_, 0, 1, 0)); #endif #ifdef HAVE_READAHEAD if (!tmpdir_on_tmpfs) { // tmpfs doesn't support readahead(2) EXPECT_OK(readahead(fd_file_, 0, 1)); } #endif } FORK_TEST_F(WithFiles, AllowedSocketSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. // recvfrom() either returns -1 with EAGAIN, or 0. int rc = recvfrom(fd_socket_, NULL, 0, MSG_DONTWAIT, NULL, NULL); if (rc < 0) { EXPECT_EQ(EAGAIN, errno); } char ch; EXPECT_OK(write(fd_file_, &ch, sizeof(ch))); // These calls will fail for lack of e.g. a proper name to send to, // but they are allowed in capability mode, so errno != ECAPMODE. EXPECT_FAIL_NOT_CAPMODE(accept(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(getpeername(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(getsockname(fd_socket_, NULL, NULL)); EXPECT_FAIL_NOT_CAPMODE(recvmsg(fd_socket_, NULL, 0)); EXPECT_FAIL_NOT_CAPMODE(sendmsg(fd_socket_, NULL, 0)); EXPECT_FAIL_NOT_CAPMODE(sendto(fd_socket_, NULL, 0, 0, NULL, 0)); off_t offset = 0; EXPECT_FAIL_NOT_CAPMODE(sendfile_(fd_socket_, fd_file_, &offset, 1)); // The socket/socketpair syscalls are allowed, but they don't give // anything externally useful (can't call bind/connect on them). int fd_socket2 = socket(PF_INET, SOCK_DGRAM, 0); EXPECT_OK(fd_socket2); if (fd_socket2 >= 0) close(fd_socket2); int fd_pair[2] = {-1, -1}; EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, fd_pair)); if (fd_pair[0] >= 0) close(fd_pair[0]); if (fd_pair[1] >= 0) close(fd_pair[1]); } #ifdef HAVE_SEND_RECV_MMSG FORK_TEST(Capmode, AllowedMmsgSendRecv) { int fd_socket = socket(PF_INET, SOCK_DGRAM, 0); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(0); addr.sin_addr.s_addr = htonl(INADDR_ANY); EXPECT_OK(bind(fd_socket, (sockaddr*)&addr, sizeof(addr))); EXPECT_OK(cap_enter()); // Enter capability mode. char buffer[256] = {0}; struct iovec iov; iov.iov_base = buffer; iov.iov_len = sizeof(buffer); struct mmsghdr mm; memset(&mm, 0, sizeof(mm)); mm.msg_hdr.msg_iov = &iov; mm.msg_hdr.msg_iovlen = 1; struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 100; EXPECT_FAIL_NOT_CAPMODE(recvmmsg(fd_socket, &mm, 1, MSG_DONTWAIT, &ts)); EXPECT_FAIL_NOT_CAPMODE(sendmmsg(fd_socket, &mm, 1, 0)); close(fd_socket); } #endif FORK_TEST(Capmode, AllowedIdentifierSyscalls) { // Record some identifiers gid_t my_gid = getgid(); pid_t my_pid = getpid(); pid_t my_ppid = getppid(); uid_t my_uid = getuid(); pid_t my_sid = getsid(my_pid); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_EQ(my_gid, getegid_()); EXPECT_EQ(my_uid, geteuid_()); EXPECT_EQ(my_gid, getgid_()); EXPECT_EQ(my_pid, getpid()); EXPECT_EQ(my_ppid, getppid()); EXPECT_EQ(my_uid, getuid_()); EXPECT_EQ(my_sid, getsid(my_pid)); gid_t grps[128]; EXPECT_OK(getgroups_(128, grps)); uid_t ruid; uid_t euid; uid_t suid; EXPECT_OK(getresuid(&ruid, &euid, &suid)); gid_t rgid; gid_t egid; gid_t sgid; EXPECT_OK(getresgid(&rgid, &egid, &sgid)); #ifdef HAVE_GETLOGIN EXPECT_TRUE(getlogin() != NULL); #endif // Set various identifiers (to their existing values). EXPECT_OK(setgid(my_gid)); #ifdef HAVE_SETFSGID EXPECT_OK(setfsgid(my_gid)); #endif EXPECT_OK(setuid(my_uid)); #ifdef HAVE_SETFSUID EXPECT_OK(setfsuid(my_uid)); #endif EXPECT_OK(setregid(my_gid, my_gid)); EXPECT_OK(setresgid(my_gid, my_gid, my_gid)); EXPECT_OK(setreuid(my_uid, my_uid)); EXPECT_OK(setresuid(my_uid, my_uid, my_uid)); EXPECT_OK(setsid()); } FORK_TEST(Capmode, AllowedSchedSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. int policy = sched_getscheduler(0); EXPECT_OK(policy); struct sched_param sp; EXPECT_OK(sched_getparam(0, &sp)); if (policy >= 0 && (!SCHED_SETSCHEDULER_REQUIRES_ROOT || getuid() == 0)) { EXPECT_OK(sched_setscheduler(0, policy, &sp)); } EXPECT_OK(sched_setparam(0, &sp)); EXPECT_OK(sched_get_priority_max(policy)); EXPECT_OK(sched_get_priority_min(policy)); struct timespec ts; EXPECT_OK(sched_rr_get_interval(0, &ts)); EXPECT_OK(sched_yield()); } FORK_TEST(Capmode, AllowedTimerSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. struct timespec ts; EXPECT_OK(clock_getres(CLOCK_REALTIME, &ts)); EXPECT_OK(clock_gettime(CLOCK_REALTIME, &ts)); struct itimerval itv; EXPECT_OK(getitimer(ITIMER_REAL, &itv)); EXPECT_OK(setitimer(ITIMER_REAL, &itv, NULL)); struct timeval tv; struct timezone tz; EXPECT_OK(gettimeofday(&tv, &tz)); ts.tv_sec = 0; ts.tv_nsec = 1; EXPECT_OK(nanosleep(&ts, NULL)); } FORK_TEST(Capmode, AllowedProfilSyscall) { EXPECT_OK(cap_enter()); // Enter capability mode. char sbuf[32]; EXPECT_OK(profil((profil_arg1_t*)sbuf, sizeof(sbuf), 0, 1)); } FORK_TEST(Capmode, AllowedResourceSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode. errno = 0; int rc = getpriority(PRIO_PROCESS, 0); EXPECT_EQ(0, errno); EXPECT_OK(setpriority(PRIO_PROCESS, 0, rc)); struct rlimit rlim; EXPECT_OK(getrlimit_(RLIMIT_CORE, &rlim)); EXPECT_OK(setrlimit(RLIMIT_CORE, &rlim)); struct rusage ruse; EXPECT_OK(getrusage(RUSAGE_SELF, &ruse)); } FORK_TEST(CapMode, AllowedMmapSyscalls) { // mmap() some memory. size_t mem_size = getpagesize(); void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); EXPECT_TRUE(mem != NULL); EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(msync(mem, mem_size, MS_ASYNC)); EXPECT_OK(madvise(mem, mem_size, MADV_NORMAL)); unsigned char vec[2]; EXPECT_OK(mincore_(mem, mem_size, vec)); EXPECT_OK(mprotect(mem, mem_size, PROT_READ|PROT_WRITE)); if (!MLOCK_REQUIRES_ROOT || getuid() == 0) { EXPECT_OK(mlock(mem, mem_size)); EXPECT_OK(munlock(mem, mem_size)); int rc = mlockall(MCL_CURRENT); if (rc != 0) { // mlockall may well fail with ENOMEM for non-root users, as the // default RLIMIT_MEMLOCK value isn't that big. EXPECT_NE(ECAPMODE, errno); } EXPECT_OK(munlockall()); } // Unmap the memory. EXPECT_OK(munmap(mem, mem_size)); } FORK_TEST(Capmode, AllowedPipeSyscalls) { EXPECT_OK(cap_enter()); // Enter capability mode int fd2[2]; int rc = pipe(fd2); EXPECT_EQ(0, rc); #ifdef HAVE_VMSPLICE char buf[11] = "0123456789"; struct iovec iov; iov.iov_base = buf; iov.iov_len = sizeof(buf); EXPECT_FAIL_NOT_CAPMODE(vmsplice(fd2[0], &iov, 1, SPLICE_F_NONBLOCK)); #endif if (rc == 0) { close(fd2[0]); close(fd2[1]); }; #ifdef HAVE_PIPE2 rc = pipe2(fd2, 0); EXPECT_EQ(0, rc); if (rc == 0) { close(fd2[0]); close(fd2[1]); }; #endif } TEST(Capmode, AllowedAtSyscalls) { int rc = mkdir(TmpFile("cap_at_syscalls"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; int dfd = open(TmpFile("cap_at_syscalls"), O_RDONLY); EXPECT_OK(dfd); int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644); EXPECT_OK(file); EXPECT_OK(close(file)); pid_t child = fork(); if (child == 0) { // Child: enter cap mode and run tests EXPECT_OK(cap_enter()); // Enter capability mode struct stat fs; EXPECT_OK(fstatat(dfd, "testfile", &fs, 0)); EXPECT_OK(mkdirat(dfd, "subdir", 0600)); EXPECT_OK(fchmodat(dfd, "subdir", 0644, 0)); EXPECT_OK(faccessat(dfd, "subdir", F_OK, 0)); EXPECT_OK(renameat(dfd, "subdir", dfd, "subdir2")); EXPECT_OK(renameat(dfd, "subdir2", dfd, "subdir")); struct timeval tv[2]; struct timezone tz; EXPECT_OK(gettimeofday(&tv[0], &tz)); EXPECT_OK(gettimeofday(&tv[1], &tz)); EXPECT_OK(futimesat(dfd, "testfile", tv)); EXPECT_OK(fchownat(dfd, "testfile", fs.st_uid, fs.st_gid, 0)); EXPECT_OK(linkat(dfd, "testfile", dfd, "linky", 0)); EXPECT_OK(symlinkat("testfile", dfd, "symlink")); char buffer[256]; EXPECT_OK(readlinkat(dfd, "symlink", buffer, sizeof(buffer))); EXPECT_OK(unlinkat(dfd, "linky", 0)); EXPECT_OK(unlinkat(dfd, "subdir", AT_REMOVEDIR)); // Check that invalid requests get a non-Capsicum errno. errno = 0; rc = readlinkat(-1, "symlink", buffer, sizeof(buffer)); EXPECT_GE(0, rc); EXPECT_NE(ECAPMODE, errno); exit(HasFailure()); } // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); // Tidy up. close(dfd); rmdir(TmpFile("cap_at_syscalls/subdir")); unlink(TmpFile("cap_at_syscalls/symlink")); unlink(TmpFile("cap_at_syscalls/linky")); unlink(TmpFile("cap_at_syscalls/testfile")); rmdir(TmpFile("cap_at_syscalls")); } TEST(Capmode, AllowedAtSyscallsCwd) { int rc = mkdir(TmpFile("cap_at_syscalls_cwd"), 0755); EXPECT_OK(rc); if (rc < 0 && errno != EEXIST) return; int dfd = open(TmpFile("cap_at_syscalls_cwd"), O_RDONLY); EXPECT_OK(dfd); int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644); EXPECT_OK(file); EXPECT_OK(close(file)); pid_t child = fork(); if (child == 0) { // Child: move into temp dir, enter cap mode and run tests EXPECT_OK(fchdir(dfd)); EXPECT_OK(cap_enter()); // Enter capability mode // Test that *at(AT_FDCWD, path,...) is policed with ECAPMODE. EXPECT_CAPMODE(openat(AT_FDCWD, "testfile", O_RDONLY)); struct stat fs; EXPECT_CAPMODE(fstatat(AT_FDCWD, "testfile", &fs, 0)); EXPECT_CAPMODE(mkdirat(AT_FDCWD, "subdir", 0600)); EXPECT_CAPMODE(fchmodat(AT_FDCWD, "subdir", 0644, 0)); EXPECT_CAPMODE(faccessat(AT_FDCWD, "subdir", F_OK, 0)); EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir", AT_FDCWD, "subdir2")); EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir2", AT_FDCWD, "subdir")); struct timeval tv[2]; struct timezone tz; EXPECT_OK(gettimeofday(&tv[0], &tz)); EXPECT_OK(gettimeofday(&tv[1], &tz)); EXPECT_CAPMODE(futimesat(AT_FDCWD, "testfile", tv)); EXPECT_CAPMODE(fchownat(AT_FDCWD, "testfile", fs.st_uid, fs.st_gid, 0)); EXPECT_CAPMODE(linkat(AT_FDCWD, "testfile", AT_FDCWD, "linky", 0)); EXPECT_CAPMODE(symlinkat("testfile", AT_FDCWD, "symlink")); char buffer[256]; EXPECT_CAPMODE(readlinkat(AT_FDCWD, "symlink", buffer, sizeof(buffer))); EXPECT_CAPMODE(unlinkat(AT_FDCWD, "linky", 0)); exit(HasFailure()); } // Wait for the child. int status; EXPECT_EQ(child, waitpid(child, &status, 0)); rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; EXPECT_EQ(0, rc); // Tidy up. close(dfd); rmdir(TmpFile("cap_at_syscalls_cwd/subdir")); unlink(TmpFile("cap_at_syscalls_cwd/symlink")); unlink(TmpFile("cap_at_syscalls_cwd/linky")); unlink(TmpFile("cap_at_syscalls_cwd/testfile")); rmdir(TmpFile("cap_at_syscalls_cwd")); } TEST(Capmode, Abort) { // Check that abort(3) works even in capability mode. pid_t child = fork(); if (child == 0) { // Child: enter capability mode and call abort(3). // Triggers something like kill(getpid(), SIGABRT). cap_enter(); // Enter capability mode. abort(); exit(99); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFSIGNALED(status)) << " status = " << std::hex << status; EXPECT_EQ(SIGABRT, WTERMSIG(status)) << " status = " << std::hex << status; } FORK_TEST_F(WithFiles, AllowedMiscSyscalls) { umask(022); mode_t um_before = umask(022); + int pipefds[2]; + EXPECT_OK(pipe(pipefds)); EXPECT_OK(cap_enter()); // Enter capability mode. mode_t um = umask(022); EXPECT_NE(-ECAPMODE, (int)um); EXPECT_EQ(um_before, um); stack_t ss; EXPECT_OK(sigaltstack(NULL, &ss)); // Finally, tests for system calls that don't fit the pattern very well. pid_t pid = fork(); EXPECT_OK(pid); if (pid == 0) { - // Child: almost immediately exit. - sleep(1); + // Child: wait for an exit message from parent (so we can test waitpid). + EXPECT_OK(close(pipefds[0])); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT); exit(0); } else if (pid > 0) { + EXPECT_OK(close(pipefds[1])); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); errno = 0; EXPECT_CAPMODE(ptrace_(PTRACE_PEEKDATA_, pid, &pid, NULL)); - EXPECT_CAPMODE(waitpid(pid, NULL, 0)); + EXPECT_CAPMODE(waitpid(pid, NULL, WNOHANG)); + SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT); + if (verbose) fprintf(stderr, " child finished\n"); } // No error return from sync(2) to test, but check errno remains unset. errno = 0; sync(); EXPECT_EQ(0, errno); // TODO(FreeBSD): ktrace #ifdef HAVE_SYSARCH // sysarch() is, by definition, architecture-dependent #if defined (__amd64__) || defined (__i386__) long sysarch_arg = 0; EXPECT_CAPMODE(sysarch(I386_SET_IOPERM, &sysarch_arg)); #else // TOOD(jra): write a test for other architectures, like arm #endif #endif } void *thread_fn(void *p) { - int delay = *(int *)p; - sleep(delay); + int fd = (int)(intptr_t)p; + if (verbose) fprintf(stderr, " thread waiting to run\n"); + AWAIT_INT_MESSAGE(fd, MSG_PARENT_CHILD_SHOULD_RUN); EXPECT_OK(getpid_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); - return NULL; + // Return whether there have been any failures to the main thread. + void *rval = (void *)(intptr_t)testing::Test::HasFailure(); + if (verbose) fprintf(stderr, " thread finished: %p\n", rval); + return rval; } // Check that restrictions are the same in subprocesses and threads FORK_TEST(Capmode, NewThread) { // Fire off a new thread before entering capability mode pthread_t early_thread; - int one = 1; // second - EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, &one)); + void *thread_rval; + // Create two pipes, one for synchronization with the threads, the other to + // synchronize with the children (since we can't use waitpid after cap_enter). + // Note: Could use pdfork+pdwait instead, but that is tested in procdesc.cc. + int thread_pipe[2]; + EXPECT_OK(pipe(thread_pipe)); + int proc_pipe[2]; + EXPECT_OK(pipe(proc_pipe)); + EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, + (void *)(intptr_t)thread_pipe[1])); // Fire off a new process before entering capability mode. + if (verbose) fprintf(stderr, " starting second child (non-capability mode)\n"); int early_child = fork(); EXPECT_OK(early_child); if (early_child == 0) { - // Child: wait and then confirm this process is unaffect by capability mode in the parent. - sleep(1); + if (verbose) fprintf(stderr, " first child started\n"); + EXPECT_OK(close(proc_pipe[0])); + // Child: wait and then confirm this process is unaffected by capability mode in the parent. + AWAIT_INT_MESSAGE(proc_pipe[1], MSG_PARENT_CHILD_SHOULD_RUN); int fd = open("/dev/null", O_RDWR); EXPECT_OK(fd); close(fd); - exit(0); + // Notify the parent of success/failure. + int rval = (int)testing::Test::HasFailure(); + SEND_INT_MESSAGE(proc_pipe[1], rval); + if (verbose) fprintf(stderr, " first child finished: %d\n", rval); + exit(rval); } EXPECT_OK(cap_enter()); // Enter capability mode. + // At this point the current process has both a child process and a + // child thread that were created before entering capability mode. + // - The child process is unaffected by capability mode. + // - The child thread is affected by capability mode. + SEND_INT_MESSAGE(proc_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN); + // Do an allowed syscall. EXPECT_OK(getpid_()); + // Wait for the first child to exit (should get a zero exit code message). + AWAIT_INT_MESSAGE(proc_pipe[0], 0); + + // The child processes/threads return HasFailure(), so we depend on no prior errors. + ASSERT_FALSE(testing::Test::HasFailure()) + << "Cannot continue test with pre-existing failures."; + // Now that we're in capability mode, if we create a second child process + // it will be affected by capability mode. + if (verbose) fprintf(stderr, " starting second child (in capability mode)\n"); int child = fork(); EXPECT_OK(child); if (child == 0) { + if (verbose) fprintf(stderr, " second child started\n"); + EXPECT_OK(close(proc_pipe[0])); // Child: do an allowed and a disallowed syscall. EXPECT_OK(getpid_()); EXPECT_CAPMODE(open("/dev/null", O_RDWR)); - exit(0); + // Notify the parent of success/failure. + int rval = (int)testing::Test::HasFailure(); + SEND_INT_MESSAGE(proc_pipe[1], rval); + if (verbose) fprintf(stderr, " second child finished: %d\n", rval); + exit(rval); } - // Don't (can't) wait for either child. - + // Now tell the early_started thread that it can run. We expect it to also + // be affected by capability mode since it's per-process not per-thread. + // Note: it is important that we don't allow the thread to run before fork(), + // since that could result in fork() being called while the thread holds one + // of the gtest-internal mutexes, so the child process deadlocks. + SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN); // Wait for the early-started thread. - EXPECT_OK(pthread_join(early_thread, NULL)); + EXPECT_OK(pthread_join(early_thread, &thread_rval)); + EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure"; - // Fire off a new thread. + // Wait for the second child to exit (should get a zero exit code message). + AWAIT_INT_MESSAGE(proc_pipe[0], 0); + + // Fire off a new (second) child thread, which is also affected by capability mode. + ASSERT_FALSE(testing::Test::HasFailure()) + << "Cannot continue test with pre-existing failures."; pthread_t child_thread; - int zero = 0; // seconds - EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, &zero)); - EXPECT_OK(pthread_join(child_thread, NULL)); + EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, + (void *)(intptr_t)thread_pipe[1])); + SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN); + EXPECT_OK(pthread_join(child_thread, &thread_rval)); + EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure"; // Fork a subprocess which fires off a new thread. + ASSERT_FALSE(testing::Test::HasFailure()) + << "Cannot continue test with pre-existing failures."; + if (verbose) fprintf(stderr, " starting third child (in capability mode)\n"); child = fork(); EXPECT_OK(child); if (child == 0) { + if (verbose) fprintf(stderr, " third child started\n"); + EXPECT_OK(close(proc_pipe[0])); pthread_t child_thread2; - EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, &zero)); - EXPECT_OK(pthread_join(child_thread2, NULL)); - exit(0); + EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, + (void *)(intptr_t)thread_pipe[1])); + SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN); + EXPECT_OK(pthread_join(child_thread2, &thread_rval)); + EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure"; + // Notify the parent of success/failure. + int rval = (int)testing::Test::HasFailure(); + SEND_INT_MESSAGE(proc_pipe[1], rval); + if (verbose) fprintf(stderr, " third child finished: %d\n", rval); + exit(rval); } - // Sleep for a bit to allow the subprocess to finish. - sleep(2); + // Wait for the third child to exit (should get a zero exit code message). + AWAIT_INT_MESSAGE(proc_pipe[0], 0); + close(proc_pipe[0]); + close(proc_pipe[1]); + close(thread_pipe[0]); + close(thread_pipe[1]); } -static int had_signal = 0; +static volatile sig_atomic_t had_signal = 0; static void handle_signal(int) { had_signal = 1; } FORK_TEST(Capmode, SelfKill) { pid_t me = getpid(); sighandler_t original = signal(SIGUSR1, handle_signal); pid_t child = fork(); if (child == 0) { // Child: sleep and exit sleep(1); exit(0); } EXPECT_OK(cap_enter()); // Enter capability mode. // Can only kill(2) to own pid. EXPECT_CAPMODE(kill(child, SIGUSR1)); EXPECT_OK(kill(me, SIGUSR1)); EXPECT_EQ(1, had_signal); signal(SIGUSR1, original); } diff --git a/capsicum-test.cc b/capsicum-test.cc index 6adb222ec055..dedad464a4d9 100644 --- a/capsicum-test.cc +++ b/capsicum-test.cc @@ -1,78 +1,116 @@ #include "capsicum-test.h" +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#include +#endif + #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 '?'; + // First check if the process exists/we have permission to see it. This + // Avoids warning messages being printed to stderr by libprocstat. + size_t len = 0; + int name[4]; + name[0] = CTL_KERN; + name[1] = KERN_PROC; + name[2] = KERN_PROC_PID; + name[3] = pid; + if (sysctl(name, nitems(name), NULL, &len, NULL, 0) < 0 && errno == ESRCH) { + if (verbose) fprintf(stderr, "Process %d does not exist\n", pid); + return '\0'; // No such process. + } + unsigned int count = 0; + struct procstat *prstat = procstat_open_sysctl(); + EXPECT_NE(NULL, prstat) << "procstat_open_sysctl failed."; + errno = 0; + struct kinfo_proc *p = procstat_getprocs(prstat, KERN_PROC_PID, pid, &count); + if (p == NULL || count == 0) { + if (verbose) fprintf(stderr, "procstat_getprocs failed with %p/%d: %s\n", p, count, strerror(errno)); + procstat_close(prstat); + return '\0'; + } + char result = '\0'; + // See state() in bin/ps/print.c + switch (p->ki_stat) { + case SSTOP: + result = 'T'; + break; + case SSLEEP: + if (p->ki_tdflags & TDF_SINTR) /* interruptable (long) */ + result = 'S'; + else + result = 'D'; + break; + case SRUN: + case SIDL: + result = 'R'; + break; + case SWAIT: + case SLOCK: + // We treat SWAIT/SLOCK as 'S' here (instead of 'W'/'L'). + result = 'S'; + break; + case SZOMB: + result = 'Z'; + break; + default: + result = '?'; + break; } + procstat_freeprocs(prstat, p); + procstat_close(prstat); + if (verbose) fprintf(stderr, "Process %d in state '%c'\n", pid, result); + return result; #endif } diff --git a/capsicum-test.h b/capsicum-test.h index 808840f4280e..7433814b31c8 100644 --- a/capsicum-test.h +++ b/capsicum-test.h @@ -1,253 +1,281 @@ /* -*- 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 = 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 { \ + SCOPED_TRACE(#C); \ EXPECT_GT(0, C); \ - EXPECT_EQ(E, errno); \ + EXPECT_EQ(E, errno) << "expected '" << strerror(E) \ + << "' but got '" << strerror(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); \ + EXPECT_NE(E, errno) << strerror(E); \ } 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 { \ + do { \ + SCOPED_TRACE(GTEST_STRINGIFY_(openat((fd), (path), (flags)))); \ 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 { \ + SCOPED_TRACE(GTEST_STRINGIFY_(openat((fd), (path), (flags)))); \ 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; \ + << #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno \ + << "(" << strerror(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'); +enum { + // Magic numbers for messages sent by child processes. + MSG_CHILD_STARTED = 1234, + MSG_CHILD_FD_RECEIVED = 4321, + // Magic numbers for messages sent by parent processes. + MSG_PARENT_REQUEST_CHILD_EXIT = 9999, + MSG_PARENT_CLOSED_FD = 10000, + MSG_PARENT_CHILD_SHOULD_RUN = 10001, +}; + +#define SEND_INT_MESSAGE(fd, message) \ + do { \ + int _msg = message; \ + EXPECT_EQ(sizeof(_msg), (size_t)write(fd, &_msg, sizeof(_msg))); \ + } while (0) + +#define AWAIT_INT_MESSAGE(fd, expected) \ + do { \ + int _msg = 0; \ + EXPECT_EQ(sizeof(_msg), (size_t)read(fd, &_msg, sizeof(_msg))); \ + EXPECT_EQ(expected, _msg); \ + } while (0) + // Mark a test that can only be run as root. #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/makefile b/makefile index 7b95e1927927..ad697f160e2e 100644 --- a/makefile +++ b/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.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) + $(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS) $(EXTRA_LIBS) # 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) $(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/openat.cc b/openat.cc index ca7e39772f9a..1f48909037bf 100644 --- a/openat.cc +++ b/openat.cc @@ -1,361 +1,362 @@ #include #include #include #include #include #include "capsicum.h" #include "capsicum-test.h" #include "syscalls.h" // Check an open call works and close the resulting fd. #define EXPECT_OPEN_OK(f) do { \ + SCOPED_TRACE(#f); \ int _fd = f; \ EXPECT_OK(_fd); \ close(_fd); \ } while (0) static void CreateFile(const char *filename, const char *contents) { int fd = open(filename, O_CREAT|O_RDWR, 0644); EXPECT_OK(fd); EXPECT_OK(write(fd, contents, strlen(contents))); close(fd); } // Test openat(2) in a variety of sitations to ensure that it obeys Capsicum // "strict relative" rules: // // 1. Use strict relative lookups in capability mode or when operating // relative to a capability. // 2. When performing strict relative lookups, absolute paths (including // symlinks to absolute paths) are not allowed, nor are paths containing // '..' components. // // These rules apply when: // - the directory FD is a Capsicum capability // - the process is in capability mode // - the openat(2) operation includes the O_BENEATH flag. FORK_TEST(Openat, Relative) { int etc = open("/etc/", O_RDONLY); EXPECT_OK(etc); cap_rights_t r_base; cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL); cap_rights_t r_ro; cap_rights_init(&r_ro, CAP_READ); cap_rights_t r_rl; cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP); int etc_cap = dup(etc); EXPECT_OK(etc_cap); EXPECT_OK(cap_rights_limit(etc_cap, &r_ro)); int etc_cap_ro = dup(etc); EXPECT_OK(etc_cap_ro); EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl)); int etc_cap_base = dup(etc); EXPECT_OK(etc_cap_base); EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base)); #ifdef HAVE_CAP_FCNTLS_LIMIT // Also limit fcntl(2) subrights. EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL)); #endif #ifdef HAVE_CAP_IOCTLS_LIMIT // Also limit ioctl(2) subrights. cap_ioctl_t ioctl_nread = FIONREAD; EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1)); #endif // openat(2) with regular file descriptors in non-capability mode // Should Just Work (tm). EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc, "../etc/passwd", O_RDONLY)); // Lookups relative to capabilities should be strictly relative. // When not in capability mode, we don't actually require CAP_LOOKUP. EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); // Performing openat(2) on a path with leading slash ignores // the provided directory FD. EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY)); // Relative lookups that go upward are not allowed. EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); // A file opened relative to a capability should itself be a capability. int fd = openat(etc_cap_base, "passwd", O_RDONLY); EXPECT_OK(fd); cap_rights_t rights; EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_base); #ifdef HAVE_CAP_FCNTLS_LIMIT cap_fcntl_t fcntls; EXPECT_OK(cap_fcntls_get(fd, &fcntls)); EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls); #endif #ifdef HAVE_CAP_IOCTLS_LIMIT cap_ioctl_t ioctls[16]; ssize_t nioctls; memset(ioctls, 0, sizeof(ioctls)); nioctls = cap_ioctls_get(fd, ioctls, 16); EXPECT_OK(nioctls); EXPECT_EQ(1, nioctls); EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]); #endif close(fd); // Enter capability mode; now ALL lookups are strictly relative. EXPECT_OK(cap_enter()); // Relative lookups on regular files or capabilities with CAP_LOOKUP // ought to succeed. EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); // Lookup relative to capabilities without CAP_LOOKUP should fail. EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY)); // Absolute lookups should fail. EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY); // Lookups containing '..' should fail in capability mode. EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); fd = openat(etc, "passwd", O_RDONLY); EXPECT_OK(fd); // A file opened relative to a capability should itself be a capability. fd = openat(etc_cap_base, "passwd", O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_base); close(fd); fd = openat(etc_cap_ro, "passwd", O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_rights_get(fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_rl); close(fd); } #define TOPDIR "cap_topdir" #define SUBDIR_ABS TOPDIR "/subdir" class OpenatTest : public ::testing::Test { public: // Build a collection of files, subdirs and symlinks: // /tmp/cap_topdir/ // /topfile // /subdir/ // /subdir/bottomfile // /symlink.samedir -> topfile // /dsymlink.samedir -> ./ // /symlink.down -> subdir/bottomfile // /dsymlink.down -> subdir/ // /symlink.absolute_in -> /tmp/cap_topdir/topfile // /dsymlink.absolute_in -> /tmp/cap_topdir/ // /symlink.absolute_out -> /etc/passwd // /dsymlink.absolute_out -> /etc/ // /symlink.relative_in -> ../../tmp/cap_topdir/topfile // /dsymlink.relative_in -> ../../tmp/cap_topdir/ // /symlink.relative_out -> ../../etc/passwd // /dsymlink.relative_out -> ../../etc/ // /subdir/symlink.up -> ../topfile // /subdir/dsymlink.up -> ../ // (In practice, this is a little more complicated because tmpdir might // not be "/tmp".) OpenatTest() { // Create a couple of nested directories int rc = mkdir(TmpFile(TOPDIR), 0755); EXPECT_OK(rc); if (rc < 0) { EXPECT_EQ(EEXIST, errno); } rc = mkdir(TmpFile(SUBDIR_ABS), 0755); EXPECT_OK(rc); if (rc < 0) { EXPECT_EQ(EEXIST, errno); } // Figure out a path prefix (like "../..") that gets us to the root // directory from TmpFile(TOPDIR). const char *p = TmpFile(TOPDIR); // maybe "/tmp/somewhere/cap_topdir" std::string dots2root = ".."; while (*p++ != '\0') { if (*p == '/') { dots2root += "/.."; } } // Create normal files in each. CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file"); CreateFile(TmpFile(SUBDIR_ABS "/bottomfile"), "File in subdirectory"); // Create various symlinks to files. EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir"))); EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down"))); EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(TOPDIR "/symlink.absolute_in"))); EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out"))); std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile"); EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in"))); std::string dots2passwd = dots2root + "/etc/passwd"; EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out"))); EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR_ABS "/symlink.up"))); // Create various symlinks to directories. EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir"))); EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down"))); EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(TOPDIR "/dsymlink.absolute_in"))); EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out"))); std::string dots2cwd = dots2root + tmpdir + "/"; EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in"))); std::string dots2etc = dots2root + "/etc/"; EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out"))); EXPECT_OK(symlink("../", TmpFile(SUBDIR_ABS "/dsymlink.up"))); // Open directory FDs for those directories and for cwd. dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY); EXPECT_OK(dir_fd_); sub_fd_ = open(TmpFile(SUBDIR_ABS), O_RDONLY); EXPECT_OK(sub_fd_); cwd_ = openat(AT_FDCWD, ".", O_RDONLY); EXPECT_OK(cwd_); // Move into the directory for the test. EXPECT_OK(fchdir(dir_fd_)); } ~OpenatTest() { fchdir(cwd_); close(cwd_); close(sub_fd_); close(dir_fd_); unlink(TmpFile(SUBDIR_ABS "/symlink.up")); unlink(TmpFile(TOPDIR "/symlink.absolute_in")); unlink(TmpFile(TOPDIR "/symlink.absolute_out")); unlink(TmpFile(TOPDIR "/symlink.relative_in")); unlink(TmpFile(TOPDIR "/symlink.relative_out")); unlink(TmpFile(TOPDIR "/symlink.down")); unlink(TmpFile(TOPDIR "/symlink.samedir")); unlink(TmpFile(SUBDIR_ABS "/dsymlink.up")); unlink(TmpFile(TOPDIR "/dsymlink.absolute_in")); unlink(TmpFile(TOPDIR "/dsymlink.absolute_out")); unlink(TmpFile(TOPDIR "/dsymlink.relative_in")); unlink(TmpFile(TOPDIR "/dsymlink.relative_out")); unlink(TmpFile(TOPDIR "/dsymlink.down")); unlink(TmpFile(TOPDIR "/dsymlink.samedir")); unlink(TmpFile(SUBDIR_ABS "/bottomfile")); unlink(TmpFile(TOPDIR "/topfile")); rmdir(TmpFile(SUBDIR_ABS)); rmdir(TmpFile(TOPDIR)); } // Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH. void CheckPolicing(int oflag) { // OK for normal access. EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag)); // Can't open paths with ".." in them. EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag); #ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT // OK for dotdot lookups that don't escape the top directory EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag)); #endif // Check that we can't escape the top directory by the cunning // ruse of going via a subdirectory. EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag); // Should only be able to open symlinks that stay within the directory. EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag)); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_in", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag); EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag)); EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag)); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag); // Although recall that O_NOFOLLOW prevents symlink following in final component. EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag)); EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag)); } protected: int dir_fd_; int sub_fd_; int cwd_; }; TEST_F(OpenatTest, WithCapability) { // Any kind of symlink can be opened relative to an ordinary directory FD. EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_in", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY)); EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY)); EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY)); // Now make both DFDs into Capsicum capabilities. cap_rights_t r_rl; cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR); EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl)); EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl)); CheckPolicing(0); // Use of AT_FDCWD is independent of use of a capability. // Can open paths starting with "/" against a capability dfd, because the dfd is ignored. } FORK_TEST_F(OpenatTest, InCapabilityMode) { EXPECT_OK(cap_enter()); // Enter capability mode CheckPolicing(0); // Use of AT_FDCWD is banned in capability mode. EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY)); EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY)); EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); // Can't open paths starting with "/" in capability mode. EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY); } #ifdef O_BENEATH TEST_F(OpenatTest, WithFlag) { CheckPolicing(O_BENEATH); // Check with AT_FDCWD. EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH)); EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_BENEATH)); // Can't open paths starting with "/" with O_BENEATH specified. EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_BENEATH); EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); } FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) { EXPECT_OK(cap_enter()); // Enter capability mode CheckPolicing(O_BENEATH); } #endif diff --git a/procdesc.cc b/procdesc.cc index 11274ce9e866..105546cabfb2 100644 --- a/procdesc.cc +++ b/procdesc.cc @@ -1,980 +1,1097 @@ // 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 volatile sig_atomic_t had_signal[NSIG]; +void clear_had_signals() { + memset(const_cast(had_signal), 0, sizeof(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; + int pipefds[2]; pid_t parent = getpid_(); + EXPECT_OK(pipe(pipefds)); 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); + close(pipefds[0]); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); + if (verbose) fprintf(stderr, "Child waiting for exit message\n"); + // Terminate once the parent has completed the checks + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT); + exit(testing::Test::HasFailure()); } - usleep(100); // ensure the child has a chance to run + close(pipefds[1]); + // Ensure the child has started. + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); + 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); + // Tell the child to exit and wait until it is a zombie. + SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT); + // EXPECT_PID_ZOMBIE waits for up to ~500ms, that should be enough time for + // the child to exit successfully. EXPECT_PID_ZOMBIE(pid); + close(pipefds[0]); // 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 *) { +static void *SubThreadMain(void *arg) { + // Notify the main thread that we have started + if (verbose) fprintf(stderr, " subthread started: pipe=%p\n", arg); + SEND_INT_MESSAGE((int)(intptr_t)arg, MSG_CHILD_STARTED); while (true) { if (verbose) fprintf(stderr, " subthread: \"I aten't dead\"\n"); usleep(100000); } return NULL; } static void *ThreadMain(void *) { int pd; + int pipefds[2]; + EXPECT_EQ(0, pipe(pipefds)); pid_t child = pdfork(&pd, 0); if (child == 0) { - // Child: start a subthread then loop + close(pipefds[0]); + // Child: start a subthread then loop. pthread_t child_subthread; - EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain, NULL)); + // Wait for the subthread startup using another pipe. + int thread_pipefds[2]; + EXPECT_EQ(0, pipe(thread_pipefds)); + EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain, + (void *)(intptr_t)thread_pipefds[0])); + if (verbose) { + fprintf(stderr, " pdforked process %d: waiting for subthread.\n", + getpid()); + } + AWAIT_INT_MESSAGE(thread_pipefds[1], MSG_CHILD_STARTED); + close(thread_pipefds[0]); + close(thread_pipefds[1]); + // Child: Notify parent that all threads have started + if (verbose) fprintf(stderr, " pdforked process %d: subthread started\n", getpid()); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); 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); + close(pipefds[1]); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); + if (verbose) fprintf(stderr, "[%d] got child startup message\n", getpid_()); // 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(); + clear_had_signals(); 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 pipefds[2]; + EXPECT_EQ(0, pipe(pipefds)); 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); + close(pipefds[0]); + // Child: wait for parent to acknowledge startup + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); + // Child: wait for two messages from the parent and the forked process + // before telling the other process to terminate. + if (verbose) fprintf(stderr, "[%d] waiting for read 1\n", getpid_()); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT); + if (verbose) fprintf(stderr, "[%d] waiting for read 2\n", getpid_()); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT); TerminateChild(); - exit(0); + if (verbose) fprintf(stderr, "[%d] about to exit\n", getpid_()); + exit(testing::Test::HasFailure()); } - usleep(100); // ensure the child has a chance to run - + close(pipefds[1]); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); + if (verbose) fprintf(stderr, "[%d] got child startup message\n", getpid_()); // 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 C: fork()ed process, in read() 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)); + // Both A and D ask C to exit, allowing it to do so. + if (verbose) fprintf(stderr, "[%d] telling child to exit\n", getpid_()); + SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT); + close(pipefds[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 C will finish its two read() calls, 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. + if (verbose) fprintf(stderr, "[%d] waiting for child to exit\n", getpid_()); 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, OtherUserIfRoot) { GTEST_SKIP_IF_NOT_ROOT(); int pd; + int status; 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. 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. + // Ideally, we should be able to send signals via a process descriptor even + // if it's owned by another user, but this is not implementated on FreeBSD. +#ifdef __FreeBSD__ + // On FreeBSD, pdkill() still performs all the same checks that kill() does + // and therefore cannot be used to send a signal to a process with another + // UID unless we are root. + EXPECT_SYSCALL_FAIL(EBADF, pdkill(pid, SIGKILL)); + EXPECT_PID_ALIVE(pid); + // However, the process will be killed when we close the process descriptor. + EXPECT_OK(close(pd)); + EXPECT_PID_GONE(pid); + // Can't pdwait4() after close() since close() reparents the child to a reaper (init) + EXPECT_SYSCALL_FAIL(EBADF, pdwait4_(pd, &status, WNOHANG, NULL)); +#else + // Sending a signal with pdkill() should be permitted 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)); +#endif } 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 pipefds[2]; + EXPECT_EQ(0, pipe(pipefds)); int pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { - // Child: watch for SIGUSR1 forever. - had_signal.clear(); + // Child: tell the parent that we have started before entering the loop, + // and importantly only do so once we have registered the SIGUSR1 handler. + close(pipefds[0]); + clear_had_signals(); signal(SIGUSR1, handle_signal); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); + // Child: watch for SIGUSR1 forever. while (!had_signal[SIGUSR1]) { usleep(100000); } exit(123); } - sleep(1); + // Wait for child to start + close(pipefds[1]); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); + close(pipefds[0]); // 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_TRUE(WIFEXITED(status)) << "status: 0x" << std::hex << status; 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 B: fork()ed process, starts a child process with pdfork() then + // waits for parent to send a shutdown message. // +--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_()); + int pipefds[2]; // for startup notification + EXPECT_OK(pipe(pipefds)); pid_t grandchild = pdfork(&pd, pdfork_flags); if (grandchild == 0) { + close(pipefds[0]); + pid_t grandchildPid = getpid_(); + EXPECT_EQ(sizeof(grandchildPid), (size_t)write(pipefds[1], &grandchildPid, sizeof(grandchildPid))); while (true) { - if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", getpid_()); + if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", grandchildPid); sleep(1); } } + close(pipefds[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); + // Wait for grandchild to start. + pid_t grandchild2; + EXPECT_EQ(sizeof(grandchild2), (size_t)read(pipefds[0], &grandchild2, sizeof(grandchild2))); + EXPECT_EQ(grandchild, grandchild2) << "received invalid grandchild pid"; + if (verbose) fprintf(stderr, " [%d] grandchild %d has started successfully\n", getpid_(), grandchild); + close(pipefds[0]); + + // Send grandchild pid to parent. + EXPECT_EQ(sizeof(grandchild), (size_t)write(sock_fds[1], &grandchild, sizeof(grandchild))); + if (verbose) fprintf(stderr, " [%d] sent grandchild pid %d to parent\n", getpid_(), grandchild); + // Wait for parent to acknowledge the message. + AWAIT_INT_MESSAGE(sock_fds[1], MSG_PARENT_REQUEST_CHILD_EXIT); + if (verbose) fprintf(stderr, " [%d] parent acknowledged grandchild pid %d\n", getpid_(), grandchild); if (verbose) fprintf(stderr, " [%d] child terminating\n", getpid_()); - exit(0); + exit(testing::Test::HasFailure()); } 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); + if (verbose) fprintf(stderr, "[%d] received grandchild id %d\n", getpid_(), grandchild); EXPECT_PID_ALIVE(child); EXPECT_PID_ALIVE(grandchild); - sleep(6); + // Tell child to exit. + if (verbose) fprintf(stderr, "[%d] telling child %d to exit\n", getpid_(), child); + SEND_INT_MESSAGE(sock_fds[0], MSG_PARENT_REQUEST_CHILD_EXIT); // 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(); + clear_had_signals(); 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(); + clear_had_signals(); 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. + EXPECT_PID_ZOMBIE(pid_); // 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(); + clear_had_signals(); int pd; + int pipefds[2]; + EXPECT_OK(pipe(pipefds)); pid_t pid = pdfork(&pd, 0); EXPECT_OK(pid); if (pid == 0) { - // Child: set a SIGINT handler and sleep. - had_signal.clear(); + // Child: set a SIGINT handler, notify the parent and sleep. + close(pipefds[0]); + clear_had_signals(); signal(SIGINT, handle_signal); + if (verbose) fprintf(stderr, "[%d] child started\n", getpid_()); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); 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); + // Note: we could receive the SIGINT just before sleep(), so we use a loop + // with a short delay instead of one long sleep(). + for (int i = 0; i < 50 && !had_signal[SIGINT]; i++) { + usleep(100000); + } + if (verbose) fprintf(stderr, "[%d] child slept, had[SIGINT]=%d\n", + getpid_(), (int)had_signal[SIGINT]); + // Return non-zero if we didn't see SIGINT. + exit(had_signal[SIGINT] ? 0 : 99); } // 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); + // Interrupt the child once it's registered the SIGINT handler. + close(pipefds[1]); + if (verbose) fprintf(stderr, "[%d] waiting for child\n", getpid_()); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); EXPECT_OK(pdkill(pd, SIGINT)); + if (verbose) fprintf(stderr, "[%d] sent SIGINT\n", getpid_()); // Make sure the child finished properly (caught signal then exited). CheckChildFinished(pid); } FORK_TEST(Pdfork, PdkillSignal) { int pd; + int pipefds[2]; + EXPECT_OK(pipe(pipefds)); 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); + close(pipefds[0]); + if (verbose) fprintf(stderr, "[%d] child started\n", getpid_()); + SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED); + // Child: wait for shutdown message. No SIGINT handler. The message should + // never be received, since SIGINT should terminate the process. + if (verbose) fprintf(stderr, "[%d] child about to read()\n", getpid_()); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT); + fprintf(stderr, "[%d] child read() returned unexpectedly\n", getpid_()); exit(99); } - + // Wait for child to start before signalling. + if (verbose) fprintf(stderr, "[%d] waiting for child\n", getpid_()); + close(pipefds[1]); + AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED); // Kill the child (as it doesn't handle SIGINT). - sleep(1); + if (verbose) fprintf(stderr, "[%d] sending SIGINT\n", getpid_()); 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_); - + SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_STARTED); // 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); + SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_FD_RECEIVED); // 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); + // Wait until the parent has closed the process descriptor. + AWAIT_INT_MESSAGE(sock_fds[0], MSG_PARENT_CLOSED_FD); + 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 + // Wait until the child has started. + AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_STARTED); // 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); + // Wait until the child has received the process descriptor. + AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_FD_RECEIVED); if (verbose) fprintf(stderr, "[%d] close process descriptor %d\n", getpid_(), pd_); close(pd_); // Not last open process descriptor + SEND_INT_MESSAGE(sock_fds[1], MSG_PARENT_CLOSED_FD); // 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_); } diff --git a/socket.cc b/socket.cc index a80cd3ae5e7e..b68af49f85e6 100644 --- a/socket.cc +++ b/socket.cc @@ -1,340 +1,350 @@ // Tests for socket functionality. #include #include #include #include #include #include #include #include "capsicum.h" #include "syscalls.h" #include "capsicum-test.h" TEST(Socket, UnixDomain) { const char* socketName = TmpFile("capsicum-test.socket"); unlink(socketName); cap_rights_t r_rw; cap_rights_init(&r_rw, CAP_READ, CAP_WRITE); cap_rights_t r_all; cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER); + int pipefds[2]; + EXPECT_EQ(0, pipe(pipefds)); pid_t child = fork(); if (child == 0) { // Child process: wait for server setup - sleep(1); + close(pipefds[0]); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_CHILD_SHOULD_RUN); // Create sockets int sock = socket(AF_UNIX, SOCK_STREAM, 0); EXPECT_OK(sock); if (sock < 0) return; int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_all = dup(sock); EXPECT_OK(cap_sock_all); EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all)); EXPECT_OK(close(sock)); // Connect socket struct sockaddr_un un; memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, socketName); socklen_t len = sizeof(un); EXPECT_NOTCAPABLE(connect_(cap_sock_rw, (struct sockaddr *)&un, len)); EXPECT_OK(connect_(cap_sock_all, (struct sockaddr *)&un, len)); exit(HasFailure()); } int sock = socket(AF_UNIX, SOCK_STREAM, 0); EXPECT_OK(sock); if (sock < 0) return; int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_all = dup(sock); EXPECT_OK(cap_sock_all); EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all)); EXPECT_OK(close(sock)); struct sockaddr_un un; memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, socketName); socklen_t len = (sizeof(un) - sizeof(un.sun_path) + strlen(un.sun_path)); // Can only bind the fully-capable socket. EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&un, len)); EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&un, len)); // Can only listen on the fully-capable socket. EXPECT_NOTCAPABLE(listen(cap_sock_rw, 3)); EXPECT_OK(listen(cap_sock_all, 3)); // Can only do socket operations on the fully-capable socket. len = sizeof(un); EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&un, &len)); int value = 0; EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_DEBUG, &value, sizeof(value))); len = sizeof(value); EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_DEBUG, &value, &len)); len = sizeof(un); memset(&un, 0, sizeof(un)); EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&un, &len)); EXPECT_EQ(AF_UNIX, un.sun_family); EXPECT_EQ(std::string(socketName), std::string(un.sun_path)); value = 0; EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, sizeof(value))); len = sizeof(value); EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, &len)); - // Accept the incoming connection + // Tell the child process that we are ready and accept the incoming connection. + EXPECT_OK(close(pipefds[1])); + SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_CHILD_SHOULD_RUN); len = sizeof(un); memset(&un, 0, sizeof(un)); EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&un, &len)); int conn_fd = accept(cap_sock_all, (struct sockaddr *)&un, &len); EXPECT_OK(conn_fd); #ifdef CAP_FROM_ACCEPT // New connection should also be a capability. cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_get(conn_fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_all); #endif // 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(conn_fd); close(cap_sock_rw); close(cap_sock_all); unlink(socketName); } TEST(Socket, TCP) { int sock = socket(AF_INET, SOCK_STREAM, 0); EXPECT_OK(sock); if (sock < 0) return; cap_rights_t r_rw; cap_rights_init(&r_rw, CAP_READ, CAP_WRITE); cap_rights_t r_all; cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER); int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_all = dup(sock); EXPECT_OK(cap_sock_all); EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all)); close(sock); struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(0); addr.sin_addr.s_addr = htonl(INADDR_ANY); socklen_t len = sizeof(addr); // Can only bind the fully-capable socket. EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&addr, len)); EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&addr, len)); getsockname(cap_sock_all, (struct sockaddr *)&addr, &len); int port = ntohs(addr.sin_port); + int pipefds[2]; + EXPECT_EQ(0, pipe(pipefds)); // Now we know the port involved, fork off a child. pid_t child = fork(); if (child == 0) { // Child process: wait for server setup - sleep(1); + close(pipefds[0]); + AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_CHILD_SHOULD_RUN); // Create sockets int sock = socket(AF_INET, SOCK_STREAM, 0); EXPECT_OK(sock); if (sock < 0) return; int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_all = dup(sock); EXPECT_OK(cap_sock_all); EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all)); close(sock); // Connect socket struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); // Pick unused port addr.sin_addr.s_addr = inet_addr("127.0.0.1"); socklen_t len = sizeof(addr); EXPECT_NOTCAPABLE(connect_(cap_sock_rw, (struct sockaddr *)&addr, len)); EXPECT_OK(connect_(cap_sock_all, (struct sockaddr *)&addr, len)); exit(HasFailure()); } // Can only listen on the fully-capable socket. EXPECT_NOTCAPABLE(listen(cap_sock_rw, 3)); EXPECT_OK(listen(cap_sock_all, 3)); // Can only do socket operations on the fully-capable socket. len = sizeof(addr); EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&addr, &len)); int value = 1; EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value))); len = sizeof(value); EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, &len)); len = sizeof(addr); memset(&addr, 0, sizeof(addr)); EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&addr, &len)); EXPECT_EQ(AF_INET, addr.sin_family); EXPECT_EQ(htons(port), addr.sin_port); value = 0; EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value))); len = sizeof(value); EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len)); - // Accept the incoming connection + // Tell the child process that we are ready and accept the incoming connection. + EXPECT_OK(close(pipefds[1])); + SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_CHILD_SHOULD_RUN); len = sizeof(addr); memset(&addr, 0, sizeof(addr)); EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&addr, &len)); int conn_fd = accept(cap_sock_all, (struct sockaddr *)&addr, &len); EXPECT_OK(conn_fd); #ifdef CAP_FROM_ACCEPT // New connection should also be a capability. cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_get(conn_fd, &rights)); EXPECT_RIGHTS_IN(&rights, &r_all); #endif // 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(conn_fd); close(cap_sock_rw); close(cap_sock_all); } TEST(Socket, UDP) { int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); EXPECT_OK(sock); if (sock < 0) return; cap_rights_t r_rw; cap_rights_init(&r_rw, CAP_READ, CAP_WRITE); cap_rights_t r_all; cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER); cap_rights_t r_connect; cap_rights_init(&r_connect, CAP_READ, CAP_WRITE, CAP_CONNECT); int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_all = dup(sock); EXPECT_OK(cap_sock_all); EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all)); close(sock); struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(0); addr.sin_addr.s_addr = htonl(INADDR_ANY); socklen_t len = sizeof(addr); // Can only bind the fully-capable socket. EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&addr, len)); EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&addr, len)); getsockname(cap_sock_all, (struct sockaddr *)&addr, &len); int port = ntohs(addr.sin_port); // Can only do socket operations on the fully-capable socket. len = sizeof(addr); EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&addr, &len)); int value = 1; EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value))); len = sizeof(value); EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, &len)); len = sizeof(addr); memset(&addr, 0, sizeof(addr)); EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&addr, &len)); EXPECT_EQ(AF_INET, addr.sin_family); EXPECT_EQ(htons(port), addr.sin_port); value = 1; EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value))); len = sizeof(value); EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len)); pid_t child = fork(); if (child == 0) { int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); EXPECT_OK(sock); int cap_sock_rw = dup(sock); EXPECT_OK(cap_sock_rw); EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw)); int cap_sock_connect = dup(sock); EXPECT_OK(cap_sock_connect); EXPECT_OK(cap_rights_limit(cap_sock_connect, &r_connect)); close(sock); // Can only sendmsg(2) to an address over a socket with CAP_CONNECT. unsigned char buffer[256]; struct iovec iov; memset(&iov, 0, sizeof(iov)); iov.iov_base = buffer; iov.iov_len = sizeof(buffer); struct msghdr mh; memset(&mh, 0, sizeof(mh)); mh.msg_iov = &iov; mh.msg_iovlen = 1; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); mh.msg_name = &addr; mh.msg_namelen = sizeof(addr); EXPECT_NOTCAPABLE(sendmsg(cap_sock_rw, &mh, 0)); EXPECT_OK(sendmsg(cap_sock_connect, &mh, 0)); #ifdef HAVE_SEND_RECV_MMSG struct mmsghdr mv; memset(&mv, 0, sizeof(mv)); memcpy(&mv.msg_hdr, &mh, sizeof(struct msghdr)); EXPECT_NOTCAPABLE(sendmmsg(cap_sock_rw, &mv, 1, 0)); EXPECT_OK(sendmmsg(cap_sock_connect, &mv, 1, 0)); #endif close(cap_sock_rw); close(cap_sock_connect); exit(HasFailure()); } // 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_sock_rw); close(cap_sock_all); }