freebsd-dev/contrib/capsicum-test/fcntl.cc
Enji Cooper 8ac5aef8f3 Integrate capsicum-test into the FreeBSD test suite
This change takes capsicum-test from upstream and applies some local changes to make the
tests work on FreeBSD when executed via Kyua.

The local modifications are as follows:
1. Make `OpenatTest.WithFlag` pass with the new dot-dot lookup behavior in FreeBSD 12.x+.
2. capsicum-test references a set of helper binaries: `mini-me`, `mini-me.noexec`, and
   `mini-me.setuid`, as part of the execve/fexecve tests, via execve, fexecve, and open.
   It achieves this upstream by assuming `mini-me*` is in the current directory, however,
   in order for Kyua to execute `capsicum-test`, it needs to provide a full path to
   `mini-me*`. In order to achieve this, I made `capsicum-test` cache the executable's
   path from argv[0] in main(..) and use the cached value to compute the path to
   `mini-me*` as part of the execve/fexecve testcases.
3. The capsicum-test test suite assumes that it's always being run on CAPABILITIES enabled
   kernels. However, there's a chance that the test will be run on a host without a
   CAPABILITIES enabled kernel, so we must check for the support before running the tests.
   The way to achieve this is to add the relevant `feature_present("security_capabilities")`
   check to SetupEnvironment::SetUp() and skip the tests when the support is not available.
   While here, add a check for `kern.trap_enotcap` being enabled. As noted by markj@ in
   https://github.com/google/capsicum-test/issues/23, this sysctl being enabled can trigger
   non-deterministic failures. Therefore, the tests should be skipped if this sysctl is
   enabled.

All local changes have been submitted to the capsicum-test project
(https://github.com/google/capsicum-test) and are in various stages of review.
Please see the following pull requests for more details:
1. https://github.com/google/capsicum-test/pull/35
2. https://github.com/google/capsicum-test/pull/41
3. https://github.com/google/capsicum-test/pull/42

Reviewed by:	asomers
Discussed with:	emaste, markj
Approved by:	emaste (mentor)
MFC after:	2 months
Differential Revision: https://reviews.freebsd.org/D19758
2019-04-01 21:24:50 +00:00

412 lines
14 KiB
C++

// Test that fcntl works in capability mode.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <map>
#include "capsicum.h"
#include "capsicum-test.h"
#include "syscalls.h"
// Ensure that fcntl() works consistently for both regular file descriptors and
// capability-wrapped ones.
FORK_TEST(Fcntl, Basic) {
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
typedef std::map<std::string, int> FileMap;
// Open some files of different types, and wrap them in capabilities.
FileMap files;
files["file"] = open("/etc/passwd", O_RDONLY);
EXPECT_OK(files["file"]);
files["socket"] = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(files["socket"]);
char shm_name[128];
sprintf(shm_name, "/capsicum-test-%d", getuid());
files["SHM"] = shm_open(shm_name, (O_CREAT|O_RDWR), 0600);
if ((files["SHM"] == -1) && errno == ENOSYS) {
// shm_open() is not implemented in user-mode Linux.
files.erase("SHM");
} else {
EXPECT_OK(files["SHM"]);
}
FileMap caps;
for (FileMap::iterator ii = files.begin(); ii != files.end(); ++ii) {
std::string key = ii->first + " cap";
caps[key] = dup(ii->second);
EXPECT_OK(cap_rights_limit(caps[key], &rights));
EXPECT_OK(caps[key]) << " on " << ii->first;
}
FileMap all(files);
all.insert(files.begin(), files.end());
EXPECT_OK(cap_enter()); // Enter capability mode.
// Ensure that we can fcntl() all the files that we opened above.
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
for (FileMap::iterator ii = all.begin(); ii != all.end(); ++ii) {
EXPECT_OK(fcntl(ii->second, F_GETFL, 0)) << " on " << ii->first;
int cap = dup(ii->second);
EXPECT_OK(cap) << " on " << ii->first;
EXPECT_OK(cap_rights_limit(cap, &r_ro)) << " on " << ii->first;
EXPECT_EQ(-1, fcntl(cap, F_GETFL, 0)) << " on " << ii->first;
EXPECT_EQ(ENOTCAPABLE, errno) << " on " << ii->first;
close(cap);
}
for (FileMap::iterator ii = all.begin(); ii != all.end(); ++ii) {
close(ii->second);
}
shm_unlink(shm_name);
}
// Supported fcntl(2) operations:
// FreeBSD10 FreeBSD9.1: Linux: Rights: Summary:
// F_DUPFD F_DUPFD F_DUPFD NONE as dup(2)
// F_DUPFD_CLOEXEC F_DUPFD_CLOEXEC NONE as dup(2) with close-on-exec
// F_DUP2FD F_DUP2FD NONE as dup2(2)
// F_DUP2FD_CLOEXEC NONE as dup2(2) with close-on-exec
// F_GETFD F_GETFD F_GETFD NONE get close-on-exec flag
// F_SETFD F_SETFD F_SETFD NONE set close-on-exec flag
// * F_GETFL F_GETFL F_GETFL FCNTL get file status flag
// * F_SETFL F_SETFL F_SETFL FCNTL set file status flag
// * F_GETOWN F_GETOWN F_GETOWN FCNTL get pid receiving SIGIO/SIGURG
// * F_SETOWN F_SETOWN F_SETOWN FCNTL set pid receiving SIGIO/SIGURG
// * F_GETOWN_EX FCNTL get pid/thread receiving SIGIO/SIGURG
// * F_SETOWN_EX FCNTL set pid/thread receiving SIGIO/SIGURG
// F_GETLK F_GETLK F_GETLK FLOCK get lock info
// F_SETLK F_SETLK F_SETLK FLOCK set lock info
// F_SETLK_REMOTE FLOCK set lock info
// F_SETLKW F_SETLKW F_SETLKW FLOCK set lock info (blocking)
// F_READAHEAD F_READAHEAD NONE set or clear readahead amount
// F_RDAHEAD F_RDAHEAD NONE set or clear readahead amount to 128KB
// F_GETSIG POLL_EVENT+FSIGNAL get signal sent when I/O possible
// F_SETSIG POLL_EVENT+FSIGNAL set signal sent when I/O possible
// F_GETLEASE FLOCK+FSIGNAL get lease on file descriptor
// F_SETLEASE FLOCK+FSIGNAL set new lease on file descriptor
// F_NOTIFY NOTIFY generate signal on changes (dnotify)
// F_GETPIPE_SZ GETSOCKOPT get pipe size
// F_SETPIPE_SZ SETSOCKOPT set pipe size
// F_GET_SEAL FSTAT get memfd seals
// F_ADD_SEAL FCHMOD set memfd seal
// If HAVE_CAP_FCNTLS_LIMIT is defined, then fcntl(2) operations that require
// CAP_FCNTL (marked with * above) can be further limited with cap_fcntls_limit(2).
namespace {
#define FCNTL_NUM_RIGHTS 9
cap_rights_t fcntl_rights[FCNTL_NUM_RIGHTS];
void InitRights() {
cap_rights_init(&(fcntl_rights[0]), 0); // Later code assumes this is at [0]
cap_rights_init(&(fcntl_rights[1]), CAP_READ, CAP_WRITE);
cap_rights_init(&(fcntl_rights[2]), CAP_FCNTL);
cap_rights_init(&(fcntl_rights[3]), CAP_FLOCK);
#ifdef CAP_FSIGNAL
cap_rights_init(&(fcntl_rights[4]), CAP_EVENT, CAP_FSIGNAL);
cap_rights_init(&(fcntl_rights[5]), CAP_FLOCK, CAP_FSIGNAL);
#else
cap_rights_init(&(fcntl_rights[4]), 0);
cap_rights_init(&(fcntl_rights[5]), 0);
#endif
#ifdef CAP_NOTIFY
cap_rights_init(&(fcntl_rights[6]), CAP_NOTIFY);
#else
cap_rights_init(&(fcntl_rights[6]), 0);
#endif
cap_rights_init(&(fcntl_rights[7]), CAP_SETSOCKOPT);
cap_rights_init(&(fcntl_rights[8]), CAP_GETSOCKOPT);
}
int CheckFcntl(unsigned long long right, int caps[FCNTL_NUM_RIGHTS], int cmd, long arg, const char* context) {
SCOPED_TRACE(context);
cap_rights_t rights;
cap_rights_init(&rights, right);
int ok_index = -1;
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
if (cap_rights_contains(&(fcntl_rights[ii]), &rights)) {
if (ok_index == -1) ok_index = ii;
continue;
}
EXPECT_NOTCAPABLE(fcntl(caps[ii], cmd, arg));
}
EXPECT_NE(-1, ok_index);
int rc = fcntl(caps[ok_index], cmd, arg);
EXPECT_OK(rc);
return rc;
}
} // namespace
#define CHECK_FCNTL(right, caps, cmd, arg) \
CheckFcntl(right, caps, cmd, arg, "fcntl(" #cmd ") expect " #right)
TEST(Fcntl, Commands) {
InitRights();
int fd = open(TmpFile("cap_fcntl_cmds"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
int sock = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(sock);
int caps[FCNTL_NUM_RIGHTS];
int sock_caps[FCNTL_NUM_RIGHTS];
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
caps[ii] = dup(fd);
EXPECT_OK(caps[ii]);
EXPECT_OK(cap_rights_limit(caps[ii], &(fcntl_rights[ii])));
sock_caps[ii] = dup(sock);
EXPECT_OK(sock_caps[ii]);
EXPECT_OK(cap_rights_limit(sock_caps[ii], &(fcntl_rights[ii])));
}
// Check the things that need no rights against caps[0].
int newfd = fcntl(caps[0], F_DUPFD, 0);
EXPECT_OK(newfd);
// dup()'ed FD should have same rights.
cap_rights_t rights;
cap_rights_init(&rights, 0);
EXPECT_OK(cap_rights_get(newfd, &rights));
EXPECT_RIGHTS_EQ(&(fcntl_rights[0]), &rights);
close(newfd);
#ifdef HAVE_F_DUP2FD
EXPECT_OK(fcntl(caps[0], F_DUP2FD, newfd));
// dup2()'ed FD should have same rights.
EXPECT_OK(cap_rights_get(newfd, &rights));
EXPECT_RIGHTS_EQ(&(fcntl_rights[0]), &rights);
close(newfd);
#endif
EXPECT_OK(fcntl(caps[0], F_GETFD, 0));
EXPECT_OK(fcntl(caps[0], F_SETFD, 0));
// Check operations that need CAP_FCNTL.
int fd_flag = CHECK_FCNTL(CAP_FCNTL, caps, F_GETFL, 0);
EXPECT_EQ(0, CHECK_FCNTL(CAP_FCNTL, caps, F_SETFL, fd_flag));
int owner = CHECK_FCNTL(CAP_FCNTL, sock_caps, F_GETOWN, 0);
EXPECT_EQ(0, CHECK_FCNTL(CAP_FCNTL, sock_caps, F_SETOWN, owner));
// Check an operation needing CAP_FLOCK.
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_EQ(0, CHECK_FCNTL(CAP_FLOCK, caps, F_GETLK, (long)&fl));
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
close(sock_caps[ii]);
close(caps[ii]);
}
close(sock);
close(fd);
unlink(TmpFile("cap_fcntl_cmds"));
}
TEST(Fcntl, WriteLock) {
int fd = open(TmpFile("cap_fcntl_readlock"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
int cap = dup(fd);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL, CAP_READ, CAP_WRITE, CAP_FLOCK);
EXPECT_OK(cap_rights_limit(cap, &rights));
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
// Write-Lock
EXPECT_OK(fcntl(cap, F_SETLK, (long)&fl));
// Check write-locked (from another process).
pid_t child = fork();
if (child == 0) {
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_OK(fcntl(fd, F_GETLK, (long)&fl));
EXPECT_NE(F_UNLCK, fl.l_type);
exit(HasFailure());
}
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
// Unlock
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_OK(fcntl(cap, F_SETLK, (long)&fl));
close(cap);
close(fd);
unlink(TmpFile("cap_fcntl_readlock"));
}
#ifdef HAVE_CAP_FCNTLS_LIMIT
TEST(Fcntl, SubRightNormalFD) {
int fd = open(TmpFile("cap_fcntl_subrightnorm"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
// Restrict the fcntl(2) subrights of a normal FD.
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
int fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_NOTCAPABLE(fcntl(fd, F_SETFL, fd_flag));
// Expect to have all capabilities.
cap_rights_t rights;
EXPECT_OK(cap_rights_get(fd, &rights));
cap_rights_t all;
CAP_SET_ALL(&all);
EXPECT_RIGHTS_EQ(&all, &rights);
cap_fcntl_t fcntls;
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// Can't widen the subrights.
EXPECT_NOTCAPABLE(cap_fcntls_limit(fd, CAP_FCNTL_GETFL|CAP_FCNTL_SETFL));
close(fd);
unlink(TmpFile("cap_fcntl_subrightnorm"));
}
TEST(Fcntl, PreserveSubRights) {
int fd = open(TmpFile("cap_fcntl_subrightpreserve"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
cap_rights_t cur_rights;
cap_fcntl_t fcntls;
EXPECT_OK(cap_rights_get(fd, &cur_rights));
EXPECT_RIGHTS_EQ(&rights, &cur_rights);
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// Limiting the top-level rights leaves the subrights unaffected...
cap_rights_clear(&rights, CAP_READ);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// ... until we remove CAP_FCNTL.
cap_rights_clear(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)0, fcntls);
EXPECT_EQ(-1, cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
close(fd);
unlink(TmpFile("cap_fcntl_subrightpreserve"));
}
TEST(Fcntl, FLSubRights) {
int fd = open(TmpFile("cap_fcntl_subrights"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
// Check operations that need CAP_FCNTL with subrights pristine => OK.
int fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_OK(fcntl(fd, F_SETFL, fd_flag));
// Check operations that need CAP_FCNTL with all subrights => OK.
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_ALL));
fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_OK(fcntl(fd, F_SETFL, fd_flag));
// Check operations that need CAP_FCNTL with specific subrights.
int fd_get = dup(fd);
int fd_set = dup(fd);
EXPECT_OK(cap_fcntls_limit(fd_get, CAP_FCNTL_GETFL));
EXPECT_OK(cap_fcntls_limit(fd_set, CAP_FCNTL_SETFL));
fd_flag = fcntl(fd_get, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_NOTCAPABLE(fcntl(fd_set, F_GETFL, 0));
EXPECT_OK(fcntl(fd_set, F_SETFL, fd_flag));
EXPECT_NOTCAPABLE(fcntl(fd_get, F_SETFL, fd_flag));
close(fd_get);
close(fd_set);
// Check operations that need CAP_FCNTL with no subrights => ENOTCAPABLE.
EXPECT_OK(cap_fcntls_limit(fd, 0));
EXPECT_NOTCAPABLE(fcntl(fd, F_GETFL, 0));
EXPECT_NOTCAPABLE(fcntl(fd, F_SETFL, fd_flag));
close(fd);
unlink(TmpFile("cap_fcntl_subrights"));
}
TEST(Fcntl, OWNSubRights) {
int sock = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(sock);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(sock, &rights));
// Check operations that need CAP_FCNTL with no subrights => OK.
int owner = fcntl(sock, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_OK(fcntl(sock, F_SETOWN, owner));
// Check operations that need CAP_FCNTL with all subrights => OK.
EXPECT_OK(cap_fcntls_limit(sock, CAP_FCNTL_ALL));
owner = fcntl(sock, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_OK(fcntl(sock, F_SETOWN, owner));
// Check operations that need CAP_FCNTL with specific subrights.
int sock_get = dup(sock);
int sock_set = dup(sock);
EXPECT_OK(cap_fcntls_limit(sock_get, CAP_FCNTL_GETOWN));
EXPECT_OK(cap_fcntls_limit(sock_set, CAP_FCNTL_SETOWN));
owner = fcntl(sock_get, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_NOTCAPABLE(fcntl(sock_set, F_GETOWN, 0));
EXPECT_OK(fcntl(sock_set, F_SETOWN, owner));
EXPECT_NOTCAPABLE(fcntl(sock_get, F_SETOWN, owner));
// Also check we can retrieve the subrights.
cap_fcntl_t fcntls;
EXPECT_OK(cap_fcntls_get(sock_get, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETOWN, fcntls);
EXPECT_OK(cap_fcntls_get(sock_set, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_SETOWN, fcntls);
// And that we can't widen the subrights.
EXPECT_NOTCAPABLE(cap_fcntls_limit(sock_get, CAP_FCNTL_GETOWN|CAP_FCNTL_SETOWN));
EXPECT_NOTCAPABLE(cap_fcntls_limit(sock_set, CAP_FCNTL_GETOWN|CAP_FCNTL_SETOWN));
close(sock_get);
close(sock_set);
// Check operations that need CAP_FCNTL with no subrights => ENOTCAPABLE.
EXPECT_OK(cap_fcntls_limit(sock, 0));
EXPECT_NOTCAPABLE(fcntl(sock, F_GETOWN, 0));
EXPECT_NOTCAPABLE(fcntl(sock, F_SETOWN, owner));
close(sock);
}
#endif