diff --git a/contrib/capsicum-test/capability-fd.cc b/contrib/capsicum-test/capability-fd.cc index f255c6425cdd..0551d9bd81ef 100644 --- a/contrib/capsicum-test/capability-fd.cc +++ b/contrib/capsicum-test/capability-fd.cc @@ -1,1344 +1,1359 @@ #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 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 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")); } + +TEST(Capability, CheckIsEmpty) { + cap_rights_t rights; + + cap_rights_init(&rights); + EXPECT_TRUE(cap_rights_is_empty(&rights)); + + size_t num_known = (sizeof(known_rights)/sizeof(known_rights[0])); + for (size_t ii = 0; ii < num_known; ii++) { + cap_rights_init(&rights, known_rights[ii].right); + EXPECT_FALSE(cap_rights_is_empty(&rights)); + cap_rights_clear(&rights, known_rights[ii].right); + EXPECT_TRUE(cap_rights_is_empty(&rights)); + } +} diff --git a/lib/libc/capability/Symbol.map b/lib/libc/capability/Symbol.map index 0deff024a046..8bf11670a5a8 100644 --- a/lib/libc/capability/Symbol.map +++ b/lib/libc/capability/Symbol.map @@ -1,10 +1,14 @@ FBSD_1.3 { __cap_rights_clear; cap_rights_contains; __cap_rights_init; __cap_rights_is_set; cap_rights_is_valid; cap_rights_merge; cap_rights_remove; __cap_rights_set; }; + +FBSD_1.8 { + cap_rights_is_empty; +}; diff --git a/lib/libc/capability/cap_rights_init.3 b/lib/libc/capability/cap_rights_init.3 index 80b522820097..98b50f653f2c 100644 --- a/lib/libc/capability/cap_rights_init.3 +++ b/lib/libc/capability/cap_rights_init.3 @@ -1,250 +1,267 @@ .\" .\" Copyright (c) 2013 The FreeBSD Foundation .\" .\" This documentation was written by Pawel Jakub Dawidek under sponsorship .\" from the FreeBSD Foundation. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 5, 2020 +.Dd November 25, 2023 .Dt CAP_RIGHTS_INIT 3 .Os .Sh NAME .Nm cap_rights_init , .Nm cap_rights_set , .Nm cap_rights_clear , .Nm cap_rights_is_set , +.Nm cap_rights_is_empty , .Nm cap_rights_is_valid , .Nm cap_rights_merge , .Nm cap_rights_remove , .Nm cap_rights_contains .Nd manage cap_rights_t structure .Sh LIBRARY .Lb libc .Sh SYNOPSIS .In sys/capsicum.h .Ft cap_rights_t * .Fn cap_rights_init "cap_rights_t *rights" "..." .Ft cap_rights_t * .Fn cap_rights_set "cap_rights_t *rights" "..." .Ft cap_rights_t * .Fn cap_rights_clear "cap_rights_t *rights" "..." .Ft bool .Fn cap_rights_is_set "const cap_rights_t *rights" "..." .Ft bool +.Fn cap_rights_is_empty "const cap_rights_t *rights" +.Ft bool .Fn cap_rights_is_valid "const cap_rights_t *rights" .Ft cap_rights_t * .Fn cap_rights_merge "cap_rights_t *dst" "const cap_rights_t *src" .Ft cap_rights_t * .Fn cap_rights_remove "cap_rights_t *dst" "const cap_rights_t *src" .Ft bool .Fn cap_rights_contains "const cap_rights_t *big" "const cap_rights_t *little" .Sh DESCRIPTION The functions documented here allow to manage the .Vt cap_rights_t structure. .Pp Capability rights should be separated with comma when passed to the .Fn cap_rights_init , .Fn cap_rights_set , .Fn cap_rights_clear and .Fn cap_rights_is_set functions. For example: .Bd -literal cap_rights_set(&rights, CAP_READ, CAP_WRITE, CAP_FSTAT, CAP_SEEK); .Ed .Pp The complete list of the capability rights can be found in the .Xr rights 4 manual page. .Pp The .Fn cap_rights_init function initialize provided .Vt cap_rights_t structure. Only properly initialized structure can be passed to the remaining functions. For convenience the structure can be filled with capability rights instead of calling the .Fn cap_rights_set function later. For even more convenience pointer to the given structure is returned, so it can be directly passed to .Xr cap_rights_limit 2 : .Bd -literal cap_rights_t rights; if (cap_rights_limit(fd, cap_rights_init(&rights, CAP_READ, CAP_WRITE)) < 0) err(1, "Unable to limit capability rights"); .Ed .Pp The .Fn cap_rights_set function adds the given capability rights to the given .Vt cap_rights_t structure. .Pp The .Fn cap_rights_clear function removes the given capability rights from the given .Vt cap_rights_t structure. .Pp The .Fn cap_rights_is_set function checks if all the given capability rights are set for the given .Vt cap_rights_t structure. .Pp The +.Fn cap_rights_is_empty +function checks if the +.Fa rights +structure is empty. +.Pp +The .Fn cap_rights_is_valid function verifies if the given .Vt cap_rights_t structure is valid. .Pp The .Fn cap_rights_merge function merges all capability rights present in the .Fa src structure into the .Fa dst structure. .Pp The .Fn cap_rights_remove function removes all capability rights present in the .Fa src structure from the .Fa dst structure. .Pp The .Fn cap_rights_contains function checks if the .Fa big structure contains all capability rights present in the .Fa little structure. .Sh RETURN VALUES The functions never fail. In case an invalid capability right or an invalid .Vt cap_rights_t structure is given as an argument, the program will be aborted. .Pp The .Fn cap_rights_init , .Fn cap_rights_set and .Fn cap_rights_clear functions return pointer to the .Vt cap_rights_t structure given in the .Fa rights argument. .Pp The .Fn cap_rights_merge and .Fn cap_rights_remove functions return pointer to the .Vt cap_rights_t structure given in the .Fa dst argument. .Pp The .Fn cap_rights_is_set returns .Va true if all the given capability rights are set in the .Fa rights argument. .Pp The +.Fn cap_rights_is_empty +function returns +.Va true +if none of the capability rights are set in the +.Fa rights +structure. +.Pp +The .Fn cap_rights_is_valid function performs various checks to see if the given .Vt cap_rights_t structure is valid and returns .Va true if it is. .Pp The .Fn cap_rights_contains function returns .Va true if all capability rights set in the .Fa little structure are also present in the .Fa big structure. .Sh EXAMPLES The following example demonstrates how to prepare a .Vt cap_rights_t structure to be passed to the .Xr cap_rights_limit 2 system call. .Bd -literal cap_rights_t rights; int fd; fd = open("/tmp/foo", O_RDWR); if (fd < 0) err(1, "open() failed"); cap_rights_init(&rights, CAP_FSTAT, CAP_READ); if (allow_write_and_seek) cap_rights_set(&rights, CAP_WRITE, CAP_SEEK); if (dont_allow_seek) cap_rights_clear(&rights, CAP_SEEK); if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) err(1, "cap_rights_limit() failed"); .Ed .Sh SEE ALSO .Xr cap_rights_limit 2 , .Xr open 2 , .Xr capsicum 4 , .Xr rights 4 .Sh HISTORY The functions .Fn cap_rights_init , .Fn cap_rights_set , .Fn cap_rights_clear , .Fn cap_rights_is_set , .Fn cap_rights_is_valid , .Fn cap_rights_merge , .Fn cap_rights_remove and .Fn cap_rights_contains first appeared in .Fx 8.3 . Support for capabilities and capabilities mode was developed as part of the .Tn TrustedBSD Project. .Sh AUTHORS This family of functions was created by .An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net under sponsorship from the FreeBSD Foundation. diff --git a/sys/kern/subr_capability.c b/sys/kern/subr_capability.c index e40c57c5307d..1f3a181a91cb 100644 --- a/sys/kern/subr_capability.c +++ b/sys/kern/subr_capability.c @@ -1,407 +1,426 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 FreeBSD Foundation * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * Note that this file is compiled into the kernel and into libc. */ #include #include #ifdef _KERNEL #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #endif #ifdef _KERNEL #define assert(exp) KASSERT((exp), ("%s:%u", __func__, __LINE__)) __read_mostly cap_rights_t cap_accept_rights; __read_mostly cap_rights_t cap_bind_rights; __read_mostly cap_rights_t cap_chflags_rights; __read_mostly cap_rights_t cap_connect_rights; __read_mostly cap_rights_t cap_event_rights; __read_mostly cap_rights_t cap_fchdir_rights; __read_mostly cap_rights_t cap_fchflags_rights; __read_mostly cap_rights_t cap_fchmod_rights; __read_mostly cap_rights_t cap_fchown_rights; __read_mostly cap_rights_t cap_fcntl_rights; __read_mostly cap_rights_t cap_fexecve_rights; __read_mostly cap_rights_t cap_flock_rights; __read_mostly cap_rights_t cap_fpathconf_rights; __read_mostly cap_rights_t cap_fstat_rights; __read_mostly cap_rights_t cap_fstatfs_rights; __read_mostly cap_rights_t cap_fsync_rights; __read_mostly cap_rights_t cap_ftruncate_rights; __read_mostly cap_rights_t cap_futimes_rights; __read_mostly cap_rights_t cap_getpeername_rights; __read_mostly cap_rights_t cap_getsockopt_rights; __read_mostly cap_rights_t cap_getsockname_rights; __read_mostly cap_rights_t cap_ioctl_rights; __read_mostly cap_rights_t cap_listen_rights; __read_mostly cap_rights_t cap_linkat_source_rights; __read_mostly cap_rights_t cap_linkat_target_rights; __read_mostly cap_rights_t cap_mmap_rights; __read_mostly cap_rights_t cap_mkdirat_rights; __read_mostly cap_rights_t cap_mkfifoat_rights; __read_mostly cap_rights_t cap_mknodat_rights; __read_mostly cap_rights_t cap_pdgetpid_rights; __read_mostly cap_rights_t cap_pdkill_rights; __read_mostly cap_rights_t cap_pread_rights; __read_mostly cap_rights_t cap_pwrite_rights; __read_mostly cap_rights_t cap_read_rights; __read_mostly cap_rights_t cap_recv_rights; __read_mostly cap_rights_t cap_renameat_source_rights; __read_mostly cap_rights_t cap_renameat_target_rights; __read_mostly cap_rights_t cap_seek_rights; __read_mostly cap_rights_t cap_send_rights; __read_mostly cap_rights_t cap_send_connect_rights; __read_mostly cap_rights_t cap_setsockopt_rights; __read_mostly cap_rights_t cap_shutdown_rights; __read_mostly cap_rights_t cap_symlinkat_rights; __read_mostly cap_rights_t cap_unlinkat_rights; __read_mostly cap_rights_t cap_write_rights; __read_mostly cap_rights_t cap_no_rights; static void cap_rights_sysinit(void *arg) { cap_rights_init_one(&cap_accept_rights, CAP_ACCEPT); cap_rights_init_one(&cap_bind_rights, CAP_BIND); cap_rights_init_one(&cap_connect_rights, CAP_CONNECT); cap_rights_init_one(&cap_event_rights, CAP_EVENT); cap_rights_init_one(&cap_fchdir_rights, CAP_FCHDIR); cap_rights_init_one(&cap_fchflags_rights, CAP_FCHFLAGS); cap_rights_init_one(&cap_fchmod_rights, CAP_FCHMOD); cap_rights_init_one(&cap_fchown_rights, CAP_FCHOWN); cap_rights_init_one(&cap_fcntl_rights, CAP_FCNTL); cap_rights_init_one(&cap_fexecve_rights, CAP_FEXECVE); cap_rights_init_one(&cap_flock_rights, CAP_FLOCK); cap_rights_init_one(&cap_fpathconf_rights, CAP_FPATHCONF); cap_rights_init_one(&cap_fstat_rights, CAP_FSTAT); cap_rights_init_one(&cap_fstatfs_rights, CAP_FSTATFS); cap_rights_init_one(&cap_fsync_rights, CAP_FSYNC); cap_rights_init_one(&cap_ftruncate_rights, CAP_FTRUNCATE); cap_rights_init_one(&cap_futimes_rights, CAP_FUTIMES); cap_rights_init_one(&cap_getpeername_rights, CAP_GETPEERNAME); cap_rights_init_one(&cap_getsockname_rights, CAP_GETSOCKNAME); cap_rights_init_one(&cap_getsockopt_rights, CAP_GETSOCKOPT); cap_rights_init_one(&cap_ioctl_rights, CAP_IOCTL); cap_rights_init_one(&cap_linkat_source_rights, CAP_LINKAT_SOURCE); cap_rights_init_one(&cap_linkat_target_rights, CAP_LINKAT_TARGET); cap_rights_init_one(&cap_listen_rights, CAP_LISTEN); cap_rights_init_one(&cap_mkdirat_rights, CAP_MKDIRAT); cap_rights_init_one(&cap_mkfifoat_rights, CAP_MKFIFOAT); cap_rights_init_one(&cap_mknodat_rights, CAP_MKNODAT); cap_rights_init_one(&cap_mmap_rights, CAP_MMAP); cap_rights_init_one(&cap_pdgetpid_rights, CAP_PDGETPID); cap_rights_init_one(&cap_pdkill_rights, CAP_PDKILL); cap_rights_init_one(&cap_pread_rights, CAP_PREAD); cap_rights_init_one(&cap_pwrite_rights, CAP_PWRITE); cap_rights_init_one(&cap_read_rights, CAP_READ); cap_rights_init_one(&cap_recv_rights, CAP_RECV); cap_rights_init_one(&cap_renameat_source_rights, CAP_RENAMEAT_SOURCE); cap_rights_init_one(&cap_renameat_target_rights, CAP_RENAMEAT_TARGET); cap_rights_init_one(&cap_seek_rights, CAP_SEEK); cap_rights_init_one(&cap_send_rights, CAP_SEND); cap_rights_init(&cap_send_connect_rights, CAP_SEND, CAP_CONNECT); cap_rights_init_one(&cap_setsockopt_rights, CAP_SETSOCKOPT); cap_rights_init_one(&cap_shutdown_rights, CAP_SHUTDOWN); cap_rights_init_one(&cap_symlinkat_rights, CAP_SYMLINKAT); cap_rights_init_one(&cap_unlinkat_rights, CAP_UNLINKAT); cap_rights_init_one(&cap_write_rights, CAP_WRITE); cap_rights_init(&cap_no_rights); } SYSINIT(cap_rights_sysinit, SI_SUB_COPYRIGHT, SI_ORDER_ANY, cap_rights_sysinit, NULL); #endif #define CAPARSIZE_MIN (CAP_RIGHTS_VERSION_00 + 2) #define CAPARSIZE_MAX (CAP_RIGHTS_VERSION + 2) static __inline int right_to_index(uint64_t right) { static const int bit2idx[] = { -1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; int idx; idx = CAPIDXBIT(right); assert(idx >= 0 && idx < sizeof(bit2idx) / sizeof(bit2idx[0])); return (bit2idx[idx]); } static void cap_rights_vset(cap_rights_t *rights, va_list ap) { uint64_t right; int i, n __unused; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); n = CAPARSIZE(rights); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (;;) { right = (uint64_t)va_arg(ap, unsigned long long); if (right == 0) break; assert(CAPRVER(right) == 0); i = right_to_index(right); assert(i >= 0); assert(i < n); assert(CAPIDXBIT(rights->cr_rights[i]) == CAPIDXBIT(right)); rights->cr_rights[i] |= right; assert(CAPIDXBIT(rights->cr_rights[i]) == CAPIDXBIT(right)); } } static void cap_rights_vclear(cap_rights_t *rights, va_list ap) { uint64_t right; int i, n __unused; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); n = CAPARSIZE(rights); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (;;) { right = (uint64_t)va_arg(ap, unsigned long long); if (right == 0) break; assert(CAPRVER(right) == 0); i = right_to_index(right); assert(i >= 0); assert(i < n); assert(CAPIDXBIT(rights->cr_rights[i]) == CAPIDXBIT(right)); rights->cr_rights[i] &= ~(right & 0x01FFFFFFFFFFFFFFULL); assert(CAPIDXBIT(rights->cr_rights[i]) == CAPIDXBIT(right)); } } static bool cap_rights_is_vset(const cap_rights_t *rights, va_list ap) { uint64_t right; int i, n __unused; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); n = CAPARSIZE(rights); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (;;) { right = (uint64_t)va_arg(ap, unsigned long long); if (right == 0) break; assert(CAPRVER(right) == 0); i = right_to_index(right); assert(i >= 0); assert(i < n); assert(CAPIDXBIT(rights->cr_rights[i]) == CAPIDXBIT(right)); if ((rights->cr_rights[i] & right) != right) return (false); } return (true); } cap_rights_t * __cap_rights_init(int version, cap_rights_t *rights, ...) { unsigned int n __unused; va_list ap; assert(version == CAP_RIGHTS_VERSION_00); n = version + 2; assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); CAP_NONE(rights); va_start(ap, rights); cap_rights_vset(rights, ap); va_end(ap); return (rights); } cap_rights_t * __cap_rights_set(cap_rights_t *rights, ...) { va_list ap; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); va_start(ap, rights); cap_rights_vset(rights, ap); va_end(ap); return (rights); } cap_rights_t * __cap_rights_clear(cap_rights_t *rights, ...) { va_list ap; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); va_start(ap, rights); cap_rights_vclear(rights, ap); va_end(ap); return (rights); } bool __cap_rights_is_set(const cap_rights_t *rights, ...) { va_list ap; bool ret; assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); va_start(ap, rights); ret = cap_rights_is_vset(rights, ap); va_end(ap); return (ret); } +bool +cap_rights_is_empty(const cap_rights_t *rights) +{ +#ifndef _KERNEL + cap_rights_t cap_no_rights; + cap_rights_init(&cap_no_rights); +#endif + + assert(CAPVER(rights) == CAP_RIGHTS_VERSION_00); + assert(CAPVER(&cap_no_rights) == CAP_RIGHTS_VERSION_00); + + for (int i = 0; i < CAPARSIZE(rights); i++) { + if (rights->cr_rights[i] != cap_no_rights.cr_rights[i]) + return (false); + } + + return (true); +} + bool cap_rights_is_valid(const cap_rights_t *rights) { cap_rights_t allrights; int i, j; if (CAPVER(rights) != CAP_RIGHTS_VERSION_00) return (false); if (CAPARSIZE(rights) < CAPARSIZE_MIN || CAPARSIZE(rights) > CAPARSIZE_MAX) { return (false); } CAP_ALL(&allrights); if (!cap_rights_contains(&allrights, rights)) return (false); for (i = 0; i < CAPARSIZE(rights); i++) { j = right_to_index(rights->cr_rights[i]); if (i != j) return (false); if (i > 0) { if (CAPRVER(rights->cr_rights[i]) != 0) return (false); } } return (true); } cap_rights_t * cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src) { unsigned int i, n; assert(CAPVER(dst) == CAP_RIGHTS_VERSION_00); assert(CAPVER(src) == CAP_RIGHTS_VERSION_00); assert(CAPVER(dst) == CAPVER(src)); assert(cap_rights_is_valid(src)); assert(cap_rights_is_valid(dst)); n = CAPARSIZE(dst); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (i = 0; i < n; i++) dst->cr_rights[i] |= src->cr_rights[i]; assert(cap_rights_is_valid(src)); assert(cap_rights_is_valid(dst)); return (dst); } cap_rights_t * cap_rights_remove(cap_rights_t *dst, const cap_rights_t *src) { unsigned int i, n; assert(CAPVER(dst) == CAP_RIGHTS_VERSION_00); assert(CAPVER(src) == CAP_RIGHTS_VERSION_00); assert(CAPVER(dst) == CAPVER(src)); assert(cap_rights_is_valid(src)); assert(cap_rights_is_valid(dst)); n = CAPARSIZE(dst); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (i = 0; i < n; i++) { dst->cr_rights[i] &= ~(src->cr_rights[i] & 0x01FFFFFFFFFFFFFFULL); } assert(cap_rights_is_valid(src)); assert(cap_rights_is_valid(dst)); return (dst); } #ifndef _KERNEL bool cap_rights_contains(const cap_rights_t *big, const cap_rights_t *little) { unsigned int i, n; assert(CAPVER(big) == CAP_RIGHTS_VERSION_00); assert(CAPVER(little) == CAP_RIGHTS_VERSION_00); assert(CAPVER(big) == CAPVER(little)); n = CAPARSIZE(big); assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX); for (i = 0; i < n; i++) { if ((big->cr_rights[i] & little->cr_rights[i]) != little->cr_rights[i]) { return (false); } } return (true); } #endif diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h index b9eb61409613..3979fd718909 100644 --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -1,508 +1,510 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008-2010, 2015 Robert N. M. Watson * Copyright (c) 2012 FreeBSD Foundation * All rights reserved. * * This software was developed at the University of Cambridge Computer * Laboratory with support from a grant from Google, Inc. * * Portions of this software were developed by Pawel Jakub Dawidek under * sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Definitions for FreeBSD capabilities facility. */ #ifndef _SYS_CAPSICUM_H_ #define _SYS_CAPSICUM_H_ #include #include #include #include #ifndef _KERNEL #include #endif #define CAPRIGHT(idx, bit) ((1ULL << (57 + (idx))) | (bit)) /* * Possible rights on capabilities. * * Notes: * Some system calls don't require a capability in order to perform an * operation on an fd. These include: close, dup, dup2. * * sendfile is authorized using CAP_READ on the file and CAP_WRITE on the * socket. * * mmap() and aio*() system calls will need special attention as they may * involve reads or writes depending a great deal on context. */ /* INDEX 0 */ /* * General file I/O. */ /* Allows for openat(O_RDONLY), read(2), readv(2). */ #define CAP_READ CAPRIGHT(0, 0x0000000000000001ULL) /* Allows for openat(O_WRONLY | O_APPEND), write(2), writev(2). */ #define CAP_WRITE CAPRIGHT(0, 0x0000000000000002ULL) /* Allows for lseek(fd, 0, SEEK_CUR). */ #define CAP_SEEK_TELL CAPRIGHT(0, 0x0000000000000004ULL) /* Allows for lseek(2). */ #define CAP_SEEK (CAP_SEEK_TELL | 0x0000000000000008ULL) /* Allows for aio_read(2), pread(2), preadv(2). */ #define CAP_PREAD (CAP_SEEK | CAP_READ) /* * Allows for aio_write(2), openat(O_WRONLY) (without O_APPEND), pwrite(2), * pwritev(2). */ #define CAP_PWRITE (CAP_SEEK | CAP_WRITE) /* Allows for mmap(PROT_NONE). */ #define CAP_MMAP CAPRIGHT(0, 0x0000000000000010ULL) /* Allows for mmap(PROT_READ). */ #define CAP_MMAP_R (CAP_MMAP | CAP_SEEK | CAP_READ) /* Allows for mmap(PROT_WRITE). */ #define CAP_MMAP_W (CAP_MMAP | CAP_SEEK | CAP_WRITE) /* Allows for mmap(PROT_EXEC). */ #define CAP_MMAP_X (CAP_MMAP | CAP_SEEK | 0x0000000000000020ULL) /* Allows for mmap(PROT_READ | PROT_WRITE). */ #define CAP_MMAP_RW (CAP_MMAP_R | CAP_MMAP_W) /* Allows for mmap(PROT_READ | PROT_EXEC). */ #define CAP_MMAP_RX (CAP_MMAP_R | CAP_MMAP_X) /* Allows for mmap(PROT_WRITE | PROT_EXEC). */ #define CAP_MMAP_WX (CAP_MMAP_W | CAP_MMAP_X) /* Allows for mmap(PROT_READ | PROT_WRITE | PROT_EXEC). */ #define CAP_MMAP_RWX (CAP_MMAP_R | CAP_MMAP_W | CAP_MMAP_X) /* Allows for openat(O_CREAT). */ #define CAP_CREATE CAPRIGHT(0, 0x0000000000000040ULL) /* Allows for openat(O_EXEC) and fexecve(2) in turn. */ #define CAP_FEXECVE CAPRIGHT(0, 0x0000000000000080ULL) /* Allows for openat(O_SYNC), openat(O_FSYNC), fsync(2), aio_fsync(2). */ #define CAP_FSYNC CAPRIGHT(0, 0x0000000000000100ULL) /* Allows for openat(O_TRUNC), ftruncate(2). */ #define CAP_FTRUNCATE CAPRIGHT(0, 0x0000000000000200ULL) /* Lookups - used to constrain *at() calls. */ #define CAP_LOOKUP CAPRIGHT(0, 0x0000000000000400ULL) /* VFS methods. */ /* Allows for fchdir(2). */ #define CAP_FCHDIR CAPRIGHT(0, 0x0000000000000800ULL) /* Allows for fchflags(2). */ #define CAP_FCHFLAGS CAPRIGHT(0, 0x0000000000001000ULL) /* Allows for fchflags(2) and chflagsat(2). */ #define CAP_CHFLAGSAT (CAP_FCHFLAGS | CAP_LOOKUP) /* Allows for fchmod(2). */ #define CAP_FCHMOD CAPRIGHT(0, 0x0000000000002000ULL) /* Allows for fchmod(2) and fchmodat(2). */ #define CAP_FCHMODAT (CAP_FCHMOD | CAP_LOOKUP) /* Allows for fchown(2). */ #define CAP_FCHOWN CAPRIGHT(0, 0x0000000000004000ULL) /* Allows for fchown(2) and fchownat(2). */ #define CAP_FCHOWNAT (CAP_FCHOWN | CAP_LOOKUP) /* Allows for fcntl(2). */ #define CAP_FCNTL CAPRIGHT(0, 0x0000000000008000ULL) /* * Allows for flock(2), openat(O_SHLOCK), openat(O_EXLOCK), * fcntl(F_SETLK_REMOTE), fcntl(F_SETLKW), fcntl(F_SETLK), fcntl(F_GETLK). */ #define CAP_FLOCK CAPRIGHT(0, 0x0000000000010000ULL) /* Allows for fpathconf(2). */ #define CAP_FPATHCONF CAPRIGHT(0, 0x0000000000020000ULL) /* Allows for UFS background-fsck operations. */ #define CAP_FSCK CAPRIGHT(0, 0x0000000000040000ULL) /* Allows for fstat(2). */ #define CAP_FSTAT CAPRIGHT(0, 0x0000000000080000ULL) /* Allows for fstat(2), fstatat(2) and faccessat(2). */ #define CAP_FSTATAT (CAP_FSTAT | CAP_LOOKUP) /* Allows for fstatfs(2). */ #define CAP_FSTATFS CAPRIGHT(0, 0x0000000000100000ULL) /* Allows for futimens(2) and futimes(2). */ #define CAP_FUTIMES CAPRIGHT(0, 0x0000000000200000ULL) /* Allows for futimens(2), futimes(2), futimesat(2) and utimensat(2). */ #define CAP_FUTIMESAT (CAP_FUTIMES | CAP_LOOKUP) /* Allows for linkat(2) (target directory descriptor). */ #define CAP_LINKAT_TARGET (CAP_LOOKUP | 0x0000000000400000ULL) /* Allows for mkdirat(2). */ #define CAP_MKDIRAT (CAP_LOOKUP | 0x0000000000800000ULL) /* Allows for mkfifoat(2). */ #define CAP_MKFIFOAT (CAP_LOOKUP | 0x0000000001000000ULL) /* Allows for mknodat(2). */ #define CAP_MKNODAT (CAP_LOOKUP | 0x0000000002000000ULL) /* Allows for renameat(2) (source directory descriptor). */ #define CAP_RENAMEAT_SOURCE (CAP_LOOKUP | 0x0000000004000000ULL) /* Allows for symlinkat(2). */ #define CAP_SYMLINKAT (CAP_LOOKUP | 0x0000000008000000ULL) /* * Allows for unlinkat(2) and renameat(2) if destination object exists and * will be removed. */ #define CAP_UNLINKAT (CAP_LOOKUP | 0x0000000010000000ULL) /* Socket operations. */ /* Allows for accept(2) and accept4(2). */ #define CAP_ACCEPT CAPRIGHT(0, 0x0000000020000000ULL) /* Allows for bind(2). */ #define CAP_BIND CAPRIGHT(0, 0x0000000040000000ULL) /* Allows for connect(2). */ #define CAP_CONNECT CAPRIGHT(0, 0x0000000080000000ULL) /* Allows for getpeername(2). */ #define CAP_GETPEERNAME CAPRIGHT(0, 0x0000000100000000ULL) /* Allows for getsockname(2). */ #define CAP_GETSOCKNAME CAPRIGHT(0, 0x0000000200000000ULL) /* Allows for getsockopt(2). */ #define CAP_GETSOCKOPT CAPRIGHT(0, 0x0000000400000000ULL) /* Allows for listen(2). */ #define CAP_LISTEN CAPRIGHT(0, 0x0000000800000000ULL) /* Allows for sctp_peeloff(2). */ #define CAP_PEELOFF CAPRIGHT(0, 0x0000001000000000ULL) #define CAP_RECV CAP_READ #define CAP_SEND CAP_WRITE /* Allows for setsockopt(2). */ #define CAP_SETSOCKOPT CAPRIGHT(0, 0x0000002000000000ULL) /* Allows for shutdown(2). */ #define CAP_SHUTDOWN CAPRIGHT(0, 0x0000004000000000ULL) /* Allows for bindat(2) on a directory descriptor. */ #define CAP_BINDAT (CAP_LOOKUP | 0x0000008000000000ULL) /* Allows for connectat(2) on a directory descriptor. */ #define CAP_CONNECTAT (CAP_LOOKUP | 0x0000010000000000ULL) /* Allows for linkat(2) (source directory descriptor). */ #define CAP_LINKAT_SOURCE (CAP_LOOKUP | 0x0000020000000000ULL) /* Allows for renameat(2) (target directory descriptor). */ #define CAP_RENAMEAT_TARGET (CAP_LOOKUP | 0x0000040000000000ULL) #define CAP_SOCK_CLIENT \ (CAP_CONNECT | CAP_GETPEERNAME | CAP_GETSOCKNAME | CAP_GETSOCKOPT | \ CAP_PEELOFF | CAP_RECV | CAP_SEND | CAP_SETSOCKOPT | CAP_SHUTDOWN) #define CAP_SOCK_SERVER \ (CAP_ACCEPT | CAP_BIND | CAP_GETPEERNAME | CAP_GETSOCKNAME | \ CAP_GETSOCKOPT | CAP_LISTEN | CAP_PEELOFF | CAP_RECV | CAP_SEND | \ CAP_SETSOCKOPT | CAP_SHUTDOWN) /* All used bits for index 0. */ #define CAP_ALL0 CAPRIGHT(0, 0x000007FFFFFFFFFFULL) /* Available bits for index 0. */ #define CAP_UNUSED0_44 CAPRIGHT(0, 0x0000080000000000ULL) /* ... */ #define CAP_UNUSED0_57 CAPRIGHT(0, 0x0100000000000000ULL) /* INDEX 1 */ /* Mandatory Access Control. */ /* Allows for mac_get_fd(3). */ #define CAP_MAC_GET CAPRIGHT(1, 0x0000000000000001ULL) /* Allows for mac_set_fd(3). */ #define CAP_MAC_SET CAPRIGHT(1, 0x0000000000000002ULL) /* Methods on semaphores. */ #define CAP_SEM_GETVALUE CAPRIGHT(1, 0x0000000000000004ULL) #define CAP_SEM_POST CAPRIGHT(1, 0x0000000000000008ULL) #define CAP_SEM_WAIT CAPRIGHT(1, 0x0000000000000010ULL) /* Allows select(2) and poll(2) on descriptor. */ #define CAP_EVENT CAPRIGHT(1, 0x0000000000000020ULL) /* Allows for kevent(2) on kqueue descriptor with eventlist != NULL. */ #define CAP_KQUEUE_EVENT CAPRIGHT(1, 0x0000000000000040ULL) /* Strange and powerful rights that should not be given lightly. */ /* Allows for ioctl(2). */ #define CAP_IOCTL CAPRIGHT(1, 0x0000000000000080ULL) #define CAP_TTYHOOK CAPRIGHT(1, 0x0000000000000100ULL) /* Process management via process descriptors. */ /* Allows for pdgetpid(2). */ #define CAP_PDGETPID CAPRIGHT(1, 0x0000000000000200ULL) /* * Allows for pdwait4(2). * * XXX: this constant was imported unused, but is targeted to be implemented * in the future (bug 235871). */ #define CAP_PDWAIT CAPRIGHT(1, 0x0000000000000400ULL) /* Allows for pdkill(2). */ #define CAP_PDKILL CAPRIGHT(1, 0x0000000000000800ULL) /* Extended attributes. */ /* Allows for extattr_delete_fd(2). */ #define CAP_EXTATTR_DELETE CAPRIGHT(1, 0x0000000000001000ULL) /* Allows for extattr_get_fd(2). */ #define CAP_EXTATTR_GET CAPRIGHT(1, 0x0000000000002000ULL) /* Allows for extattr_list_fd(2). */ #define CAP_EXTATTR_LIST CAPRIGHT(1, 0x0000000000004000ULL) /* Allows for extattr_set_fd(2). */ #define CAP_EXTATTR_SET CAPRIGHT(1, 0x0000000000008000ULL) /* Access Control Lists. */ /* Allows for acl_valid_fd_np(3). */ #define CAP_ACL_CHECK CAPRIGHT(1, 0x0000000000010000ULL) /* Allows for acl_delete_fd_np(3). */ #define CAP_ACL_DELETE CAPRIGHT(1, 0x0000000000020000ULL) /* Allows for acl_get_fd(3) and acl_get_fd_np(3). */ #define CAP_ACL_GET CAPRIGHT(1, 0x0000000000040000ULL) /* Allows for acl_set_fd(3) and acl_set_fd_np(3). */ #define CAP_ACL_SET CAPRIGHT(1, 0x0000000000080000ULL) /* Allows for kevent(2) on kqueue descriptor with changelist != NULL. */ #define CAP_KQUEUE_CHANGE CAPRIGHT(1, 0x0000000000100000ULL) #define CAP_KQUEUE (CAP_KQUEUE_EVENT | CAP_KQUEUE_CHANGE) /* All used bits for index 1. */ #define CAP_ALL1 CAPRIGHT(1, 0x00000000001FFFFFULL) /* Available bits for index 1. */ #define CAP_UNUSED1_22 CAPRIGHT(1, 0x0000000000200000ULL) /* ... */ #define CAP_UNUSED1_57 CAPRIGHT(1, 0x0100000000000000ULL) /* Backward compatibility. */ #define CAP_POLL_EVENT CAP_EVENT #define CAP_ALL(rights) do { \ (rights)->cr_rights[0] = \ ((uint64_t)CAP_RIGHTS_VERSION << 62) | CAP_ALL0; \ (rights)->cr_rights[1] = CAP_ALL1; \ } while (0) #define CAP_NONE(rights) do { \ (rights)->cr_rights[0] = \ ((uint64_t)CAP_RIGHTS_VERSION << 62) | CAPRIGHT(0, 0ULL); \ (rights)->cr_rights[1] = CAPRIGHT(1, 0ULL); \ } while (0) #define CAPRVER(right) ((int)((right) >> 62)) #define CAPVER(rights) CAPRVER((rights)->cr_rights[0]) #define CAPARSIZE(rights) (CAPVER(rights) + 2) #define CAPIDXBIT(right) ((int)(((right) >> 57) & 0x1F)) /* * Allowed fcntl(2) commands. */ #define CAP_FCNTL_GETFL (1 << F_GETFL) #define CAP_FCNTL_SETFL (1 << F_SETFL) #define CAP_FCNTL_GETOWN (1 << F_GETOWN) #define CAP_FCNTL_SETOWN (1 << F_SETOWN) #define CAP_FCNTL_ALL (CAP_FCNTL_GETFL | CAP_FCNTL_SETFL | \ CAP_FCNTL_GETOWN | CAP_FCNTL_SETOWN) #define CAP_IOCTLS_ALL SSIZE_MAX __BEGIN_DECLS #define cap_rights_init(...) \ __cap_rights_init(CAP_RIGHTS_VERSION, __VA_ARGS__, 0ULL) cap_rights_t *__cap_rights_init(int version, cap_rights_t *rights, ...); #define cap_rights_set(...) \ __cap_rights_set(__VA_ARGS__, 0ULL) cap_rights_t *__cap_rights_set(cap_rights_t *rights, ...); #define cap_rights_clear(...) \ __cap_rights_clear(__VA_ARGS__, 0ULL) cap_rights_t *__cap_rights_clear(cap_rights_t *rights, ...); #define cap_rights_is_set(...) \ __cap_rights_is_set(__VA_ARGS__, 0ULL) bool __cap_rights_is_set(const cap_rights_t *rights, ...); +bool cap_rights_is_empty(const cap_rights_t *rights); + bool cap_rights_is_valid(const cap_rights_t *rights); cap_rights_t *cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src); cap_rights_t *cap_rights_remove(cap_rights_t *dst, const cap_rights_t *src); #ifdef _KERNEL /* * We only support one size to reduce branching. */ _Static_assert(CAP_RIGHTS_VERSION == CAP_RIGHTS_VERSION_00, "unsupported version of capsicum rights"); #define cap_rights_init_zero(r) ({ \ cap_rights_t *_r = (r); \ CAP_NONE(_r); \ _r; \ }) #define cap_rights_init_one(r, right) ({ \ CTASSERT(CAPRVER(right) == CAP_RIGHTS_VERSION); \ cap_rights_t *_r = (r); \ CAP_NONE(_r); \ _r->cr_rights[CAPIDXBIT(right) - 1] |= right; \ _r; \ }) #define cap_rights_set_one(r, right) ({ \ CTASSERT(CAPRVER(right) == CAP_RIGHTS_VERSION); \ cap_rights_t *_r = (r); \ _r->cr_rights[CAPIDXBIT(right) - 1] |= right; \ _r; \ }) /* * Allow checking caps which are possibly getting modified at the same time. * The caller is expected to determine whether the result is legitimate via * other means, see fget_unlocked for an example. */ static inline bool cap_rights_contains_transient(const cap_rights_t *big, const cap_rights_t *little) { if (__predict_true( (big->cr_rights[0] & little->cr_rights[0]) == little->cr_rights[0] && (big->cr_rights[1] & little->cr_rights[1]) == little->cr_rights[1])) return (true); return (false); } #define cap_rights_contains cap_rights_contains_transient int cap_check_failed_notcapable(const cap_rights_t *havep, const cap_rights_t *needp); static inline int cap_check_inline(const cap_rights_t *havep, const cap_rights_t *needp) { if (__predict_false(!cap_rights_contains(havep, needp))) return (cap_check_failed_notcapable(havep, needp)); return (0); } static inline int cap_check_inline_transient(const cap_rights_t *havep, const cap_rights_t *needp) { if (__predict_false(!cap_rights_contains(havep, needp))) return (1); return (0); } #else bool cap_rights_contains(const cap_rights_t *big, const cap_rights_t *little); #endif __END_DECLS #ifdef _KERNEL #include #define IN_CAPABILITY_MODE(td) (((td)->td_ucred->cr_flags & CRED_FLAG_CAPMODE) != 0) struct filedesc; struct filedescent; /* * Test whether a capability grants the requested rights. */ int cap_check(const cap_rights_t *havep, const cap_rights_t *needp); /* * Convert capability rights into VM access flags. */ vm_prot_t cap_rights_to_vmprot(const cap_rights_t *havep); /* * For the purposes of procstat(1) and similar tools, allow kern_descrip.c to * extract the rights from a capability. * * Dereferencing fdep requires filedesc.h, but including it would cause * significant pollution. Instead add a macro for consumers which want it, * most notably kern_descrip.c. */ #define cap_rights_fde_inline(fdep) (&(fdep)->fde_rights) const cap_rights_t *cap_rights_fde(const struct filedescent *fde); const cap_rights_t *cap_rights(struct filedesc *fdp, int fd); int cap_ioctl_check(struct filedesc *fdp, int fd, u_long cmd); int cap_fcntl_check_fde(struct filedescent *fde, int cmd); int cap_fcntl_check(struct filedesc *fdp, int fd, int cmd); extern bool trap_enotcap; #else /* !_KERNEL */ __BEGIN_DECLS /* * cap_enter(): Cause the process to enter capability mode, which will * prevent it from directly accessing global namespaces. System calls will * be limited to process-local, process-inherited, or file descriptor * operations. If already in capability mode, a no-op. */ int cap_enter(void); /* * Are we sandboxed (in capability mode)? * This is a libc wrapper around the cap_getmode(2) system call. */ bool cap_sandboxed(void); /* * cap_getmode(): Are we in capability mode? */ int cap_getmode(u_int *modep); /* * Limits capability rights for the given descriptor (CAP_*). */ int cap_rights_limit(int fd, const cap_rights_t *rights); /* * Returns capability rights for the given descriptor. */ #define cap_rights_get(fd, rights) \ __cap_rights_get(CAP_RIGHTS_VERSION, (fd), (rights)) int __cap_rights_get(int version, int fd, cap_rights_t *rights); /* * Limits allowed ioctls for the given descriptor. */ int cap_ioctls_limit(int fd, const cap_ioctl_t *cmds, size_t ncmds); /* * Returns array of allowed ioctls for the given descriptor. * If all ioctls are allowed, the cmds array is not populated and * the function returns CAP_IOCTLS_ALL. */ ssize_t cap_ioctls_get(int fd, cap_ioctl_t *cmds, size_t maxcmds); /* * Limits allowed fcntls for the given descriptor (CAP_FCNTL_*). */ int cap_fcntls_limit(int fd, uint32_t fcntlrights); /* * Returns bitmask of allowed fcntls for the given descriptor. */ int cap_fcntls_get(int fd, uint32_t *fcntlrightsp); __END_DECLS #endif /* !_KERNEL */ #endif /* !_SYS_CAPSICUM_H_ */