Changeset View
Changeset View
Standalone View
Standalone View
contrib/capsicum-test/openat.cc
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <fcntl.h> | |||||
#include <sys/ioctl.h> | |||||
#include <string> | |||||
#include "capsicum.h" | |||||
#include "capsicum-test.h" | |||||
#include "syscalls.h" | |||||
// Check an open call works and close the resulting fd. | |||||
#define EXPECT_OPEN_OK(f) do { \ | |||||
int _fd = f; \ | |||||
EXPECT_OK(_fd); \ | |||||
close(_fd); \ | |||||
} while (0) | |||||
static void CreateFile(const char *filename, const char *contents) { | |||||
int fd = open(filename, O_CREAT|O_RDWR, 0644); | |||||
EXPECT_OK(fd); | |||||
EXPECT_OK(write(fd, contents, strlen(contents))); | |||||
close(fd); | |||||
} | |||||
// Test openat(2) in a variety of sitations to ensure that it obeys Capsicum | |||||
// "strict relative" rules: | |||||
// | |||||
// 1. Use strict relative lookups in capability mode or when operating | |||||
// relative to a capability. | |||||
// 2. When performing strict relative lookups, absolute paths (including | |||||
// symlinks to absolute paths) are not allowed, nor are paths containing | |||||
// '..' components. | |||||
// | |||||
// These rules apply when: | |||||
// - the directory FD is a Capsicum capability | |||||
// - the process is in capability mode | |||||
// - the openat(2) operation includes the O_BENEATH flag. | |||||
FORK_TEST(Openat, Relative) { | |||||
int etc = open("/etc/", O_RDONLY); | |||||
EXPECT_OK(etc); | |||||
cap_rights_t r_base; | |||||
cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL); | |||||
cap_rights_t r_ro; | |||||
cap_rights_init(&r_ro, CAP_READ); | |||||
cap_rights_t r_rl; | |||||
cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP); | |||||
int etc_cap = dup(etc); | |||||
EXPECT_OK(etc_cap); | |||||
EXPECT_OK(cap_rights_limit(etc_cap, &r_ro)); | |||||
int etc_cap_ro = dup(etc); | |||||
EXPECT_OK(etc_cap_ro); | |||||
EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl)); | |||||
int etc_cap_base = dup(etc); | |||||
EXPECT_OK(etc_cap_base); | |||||
EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base)); | |||||
#ifdef HAVE_CAP_FCNTLS_LIMIT | |||||
// Also limit fcntl(2) subrights. | |||||
EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL)); | |||||
#endif | |||||
#ifdef HAVE_CAP_IOCTLS_LIMIT | |||||
// Also limit ioctl(2) subrights. | |||||
cap_ioctl_t ioctl_nread = FIONREAD; | |||||
EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1)); | |||||
#endif | |||||
// openat(2) with regular file descriptors in non-capability mode | |||||
// Should Just Work (tm). | |||||
EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc, "../etc/passwd", O_RDONLY)); | |||||
// Lookups relative to capabilities should be strictly relative. | |||||
// When not in capability mode, we don't actually require CAP_LOOKUP. | |||||
EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); | |||||
// Performing openat(2) on a path with leading slash ignores | |||||
// the provided directory FD. | |||||
EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY)); | |||||
// Relative lookups that go upward are not allowed. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); | |||||
// A file opened relative to a capability should itself be a capability. | |||||
int fd = openat(etc_cap_base, "passwd", O_RDONLY); | |||||
EXPECT_OK(fd); | |||||
cap_rights_t rights; | |||||
EXPECT_OK(cap_rights_get(fd, &rights)); | |||||
EXPECT_RIGHTS_IN(&rights, &r_base); | |||||
#ifdef HAVE_CAP_FCNTLS_LIMIT | |||||
cap_fcntl_t fcntls; | |||||
EXPECT_OK(cap_fcntls_get(fd, &fcntls)); | |||||
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls); | |||||
#endif | |||||
#ifdef HAVE_CAP_IOCTLS_LIMIT | |||||
cap_ioctl_t ioctls[16]; | |||||
ssize_t nioctls; | |||||
memset(ioctls, 0, sizeof(ioctls)); | |||||
nioctls = cap_ioctls_get(fd, ioctls, 16); | |||||
EXPECT_OK(nioctls); | |||||
EXPECT_EQ(1, nioctls); | |||||
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]); | |||||
#endif | |||||
close(fd); | |||||
// Enter capability mode; now ALL lookups are strictly relative. | |||||
EXPECT_OK(cap_enter()); | |||||
// Relative lookups on regular files or capabilities with CAP_LOOKUP | |||||
// ought to succeed. | |||||
EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); | |||||
// Lookup relative to capabilities without CAP_LOOKUP should fail. | |||||
EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY)); | |||||
// Absolute lookups should fail. | |||||
EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY); | |||||
// Lookups containing '..' should fail in capability mode. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); | |||||
fd = openat(etc, "passwd", O_RDONLY); | |||||
EXPECT_OK(fd); | |||||
// A file opened relative to a capability should itself be a capability. | |||||
fd = openat(etc_cap_base, "passwd", O_RDONLY); | |||||
EXPECT_OK(fd); | |||||
EXPECT_OK(cap_rights_get(fd, &rights)); | |||||
EXPECT_RIGHTS_IN(&rights, &r_base); | |||||
close(fd); | |||||
fd = openat(etc_cap_ro, "passwd", O_RDONLY); | |||||
EXPECT_OK(fd); | |||||
EXPECT_OK(cap_rights_get(fd, &rights)); | |||||
EXPECT_RIGHTS_IN(&rights, &r_rl); | |||||
close(fd); | |||||
} | |||||
#define TOPDIR "cap_topdir" | |||||
#define SUBDIR TOPDIR "/subdir" | |||||
class OpenatTest : public ::testing::Test { | |||||
public: | |||||
// Build a collection of files, subdirs and symlinks: | |||||
// /tmp/cap_topdir/ | |||||
// /topfile | |||||
// /subdir/ | |||||
// /subdir/bottomfile | |||||
// /symlink.samedir -> topfile | |||||
// /dsymlink.samedir -> ./ | |||||
// /symlink.down -> subdir/bottomfile | |||||
// /dsymlink.down -> subdir/ | |||||
// /symlink.absolute_out -> /etc/passwd | |||||
// /dsymlink.absolute_out -> /etc/ | |||||
// /symlink.relative_in -> ../../tmp/cap_topdir/topfile | |||||
// /dsymlink.relative_in -> ../../tmp/cap_topdir/ | |||||
// /symlink.relative_out -> ../../etc/passwd | |||||
// /dsymlink.relative_out -> ../../etc/ | |||||
// /subdir/dsymlink.absolute_in -> /tmp/cap_topdir/ | |||||
// /subdir/dsymlink.up -> ../ | |||||
// /subdir/symlink.absolute_in -> /tmp/cap_topdir/topfile | |||||
// /subdir/symlink.up -> ../topfile | |||||
// (In practice, this is a little more complicated because tmpdir might | |||||
// not be "/tmp".) | |||||
OpenatTest() { | |||||
// Create a couple of nested directories | |||||
int rc = mkdir(TmpFile(TOPDIR), 0755); | |||||
EXPECT_OK(rc); | |||||
if (rc < 0) { | |||||
EXPECT_EQ(EEXIST, errno); | |||||
} | |||||
rc = mkdir(TmpFile(SUBDIR), 0755); | |||||
EXPECT_OK(rc); | |||||
if (rc < 0) { | |||||
EXPECT_EQ(EEXIST, errno); | |||||
} | |||||
// Figure out a path prefix (like "../..") that gets us to the root | |||||
// directory from TmpFile(TOPDIR). | |||||
const char *p = TmpFile(TOPDIR); // maybe "/tmp/somewhere/cap_topdir" | |||||
std::string dots2root = ".."; | |||||
while (*p++ != '\0') { | |||||
if (*p == '/') { | |||||
dots2root += "/.."; | |||||
} | |||||
} | |||||
// Create normal files in each. | |||||
CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file"); | |||||
CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory"); | |||||
// Create various symlinks to files. | |||||
EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir"))); | |||||
EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down"))); | |||||
EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(SUBDIR "/symlink.absolute_in"))); | |||||
EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out"))); | |||||
std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile"); | |||||
EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in"))); | |||||
std::string dots2passwd = dots2root + "/etc/passwd"; | |||||
EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out"))); | |||||
EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR "/symlink.up"))); | |||||
// Create various symlinks to directories. | |||||
EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir"))); | |||||
EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down"))); | |||||
EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(SUBDIR "/dsymlink.absolute_in"))); | |||||
EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out"))); | |||||
std::string dots2cwd = dots2root + tmpdir + "/"; | |||||
EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in"))); | |||||
std::string dots2etc = dots2root + "/etc/"; | |||||
EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out"))); | |||||
EXPECT_OK(symlink("../", TmpFile(SUBDIR "/dsymlink.up"))); | |||||
// Open directory FDs for those directories and for cwd. | |||||
dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY); | |||||
EXPECT_OK(dir_fd_); | |||||
sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY); | |||||
EXPECT_OK(sub_fd_); | |||||
cwd_ = openat(AT_FDCWD, ".", O_RDONLY); | |||||
EXPECT_OK(cwd_); | |||||
// Move into the directory for the test. | |||||
EXPECT_OK(fchdir(dir_fd_)); | |||||
} | |||||
~OpenatTest() { | |||||
fchdir(cwd_); | |||||
close(cwd_); | |||||
close(sub_fd_); | |||||
close(dir_fd_); | |||||
unlink(TmpFile(SUBDIR "/symlink.up")); | |||||
unlink(TmpFile(SUBDIR "/symlink.absolute_in")); | |||||
unlink(TmpFile(TOPDIR "/symlink.absolute_out")); | |||||
unlink(TmpFile(TOPDIR "/symlink.relative_in")); | |||||
unlink(TmpFile(TOPDIR "/symlink.relative_out")); | |||||
unlink(TmpFile(TOPDIR "/symlink.down")); | |||||
unlink(TmpFile(TOPDIR "/symlink.samedir")); | |||||
unlink(TmpFile(SUBDIR "/dsymlink.up")); | |||||
unlink(TmpFile(SUBDIR "/dsymlink.absolute_in")); | |||||
unlink(TmpFile(TOPDIR "/dsymlink.absolute_out")); | |||||
unlink(TmpFile(TOPDIR "/dsymlink.relative_in")); | |||||
unlink(TmpFile(TOPDIR "/dsymlink.relative_out")); | |||||
unlink(TmpFile(TOPDIR "/dsymlink.down")); | |||||
unlink(TmpFile(TOPDIR "/dsymlink.samedir")); | |||||
unlink(TmpFile(SUBDIR "/bottomfile")); | |||||
unlink(TmpFile(TOPDIR "/topfile")); | |||||
rmdir(TmpFile(SUBDIR)); | |||||
rmdir(TmpFile(TOPDIR)); | |||||
} | |||||
// Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH. | |||||
void CheckPolicing(int oflag) { | |||||
// OK for normal access. | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag)); | |||||
EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag)); | |||||
EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag)); | |||||
// Can't open paths with ".." in them. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag); | |||||
#ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT | |||||
// OK for dotdot lookups that don't escape the top directory | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag)); | |||||
#endif | |||||
// Check that we can't escape the top directory by the cunning | |||||
// ruse of going via a subdirectory. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag); | |||||
// Should only be able to open symlinks that stay within the directory. | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag)); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.absolute_in", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag)); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag); | |||||
// Although recall that O_NOFOLLOW prevents symlink following in final component. | |||||
EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag)); | |||||
EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag)); | |||||
} | |||||
protected: | |||||
int dir_fd_; | |||||
int sub_fd_; | |||||
int cwd_; | |||||
}; | |||||
TEST_F(OpenatTest, WithCapability) { | |||||
// Any kind of symlink can be opened relative to an ordinary directory FD. | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(sub_fd_, "symlink.absolute_in", O_RDONLY)); | |||||
EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY)); | |||||
// Now make both DFDs into Capsicum capabilities. | |||||
cap_rights_t r_rl; | |||||
cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR); | |||||
EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl)); | |||||
EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl)); | |||||
CheckPolicing(0); | |||||
// Use of AT_FDCWD is independent of use of a capability. | |||||
// Can open paths starting with "/" against a capability dfd, because the dfd is ignored. | |||||
} | |||||
FORK_TEST_F(OpenatTest, InCapabilityMode) { | |||||
EXPECT_OK(cap_enter()); // Enter capability mode | |||||
CheckPolicing(0); | |||||
// Use of AT_FDCWD is banned in capability mode. | |||||
EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY)); | |||||
EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY)); | |||||
EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); | |||||
// Can't open paths starting with "/" in capability mode. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY); | |||||
} | |||||
#ifdef O_BENEATH | |||||
TEST_F(OpenatTest, WithFlag) { | |||||
CheckPolicing(O_BENEATH); | |||||
// Check with AT_FDCWD. | |||||
EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH)); | |||||
EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_BENEATH)); | |||||
// Can't open paths starting with "/" with O_BENEATH specified. | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_BENEATH); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); | |||||
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_BENEATH); | |||||
} | |||||
FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) { | |||||
EXPECT_OK(cap_enter()); // Enter capability mode | |||||
CheckPolicing(O_BENEATH); | |||||
} | |||||
#endif |