#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"
#define EXPECT_OPEN_OK(f) do { \
SCOPED_TRACE(#f); \
int _fd = f; \
EXPECT_OK(_fd); \
close(_fd); \
} while (0)
static void CreateFile(const char *filename, const char *contents) {
int fd = open(filename, O_CREAT|O_RDWR, 0644);
EXPECT_OK(fd);
EXPECT_OK(write(fd, contents, strlen(contents)));
close(fd);
}
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
EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL));
#endif
#ifdef HAVE_CAP_IOCTLS_LIMIT
cap_ioctl_t ioctl_nread = FIONREAD;
EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1));
#endif
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));
EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_base, "/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);
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);
EXPECT_OK(cap_enter());
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));
EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY));
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);
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);
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:
OpenatTest() {
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);
}
const char *p = TmpFile(TOPDIR);
std::string dots2root = "..";
while (*p++ != '\0') {
if (*p == '/') {
dots2root += "/..";
}
}
CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file");
CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory");
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")));
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")));
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_);
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));
}
void CheckPolicing(int oflag) {
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));
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
EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag));
#endif
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag);
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);
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) {
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));
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);
}
FORK_TEST_F(OpenatTest, InCapabilityMode) {
EXPECT_OK(cap_enter());
CheckPolicing(0);
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));
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY);
}
#if !defined(O_RESOLVE_BENEATH) && defined(O_BENEATH)
#define O_RESOLVE_BENEATH O_BENEATH
#endif
#ifdef O_RESOLVE_BENEATH
TEST_F(OpenatTest, WithFlag) {
CheckPolicing(O_RESOLVE_BENEATH);
EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_RESOLVE_BENEATH));
EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_RESOLVE_BENEATH));
EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
}
FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) {
EXPECT_OK(cap_enter());
CheckPolicing(O_RESOLVE_BENEATH);
}
#endif