freebsd-dev/contrib/capsicum-test/capability-fd.cc
Alex Richardson 955a3f9ad5 Update capsicum-test to git commit f4d97414d48b8f8356b971ab9f45dc5c70d53c40
This includes various fixes that I submitted recently such as updating the
pdkill() tests for the actual implemented behaviour
(https://github.com/google/capsicum-test/pull/53) and lots of changes to
avoid calling sleep() and replacing it with reliable synchronization
(pull requests 49,51,52,53,54). This should make the testsuite more reliable
when running on Jenkins. Additionally, process status is now retrieved using
libprocstat instead of running `ps` and parsing the output
(https://github.com/google/capsicum-test/pull/50). This fixes one previously
failing test and speeds up execution.

Overall, this update reduces the total runtime from ~60s to about 4-5 seconds.
2021-03-02 16:38:05 +00:00

1345 lines
44 KiB
C++

#include <stdio.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdint.h>
#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
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;
int rc = sendmsg(sock_fds[1], &mh, 0);
EXPECT_OK(rc);
// 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 <uid> 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"));
}