Index: projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc =================================================================== --- projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc (revision 345648) +++ projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc (revision 345649) @@ -1,101 +1,123 @@ #include #ifdef __linux__ #include #include #endif #include #include +#include #include #include #include +#include #include #include "gtest/gtest.h" #include "capsicum-test.h" std::string tmpdir; class SetupEnvironment : public ::testing::Environment { public: SetupEnvironment() : teardown_tmpdir_(false) {} void SetUp() override { if (tmpdir.empty()) { std::cerr << "Generating temporary directory root: "; CreateTemporaryRoot(); } else { std::cerr << "User provided temporary directory root: "; } std::cerr << tmpdir << std::endl; } void CreateTemporaryRoot() { char *tmpdir_name = tempnam(nullptr, "cptst"); ASSERT_NE(tmpdir_name, nullptr); ASSERT_EQ(mkdir(tmpdir_name, 0700), 0) << "Could not create temp directory, " << tmpdir_name << ": " << strerror(errno); tmpdir = std::string(tmpdir_name); free(tmpdir_name); teardown_tmpdir_ = true; } void TearDown() override { if (teardown_tmpdir_) { rmdir(tmpdir.c_str()); } } private: bool teardown_tmpdir_; }; +std::string capsicum_test_bindir; + int main(int argc, char* argv[]) { + // Set up the test program path, so capsicum-test can find programs, like + // mini-me* when executed from an absolute path. + { + char *new_path, *old_path, *program_name; + + program_name = strdup(argv[0]); + assert(program_name); + capsicum_test_bindir = std::string(dirname(program_name)); + free(program_name); + + old_path = getenv("PATH"); + assert(old_path); + + assert(asprintf(&new_path, "%s:%s", capsicum_test_bindir.c_str(), + old_path) > 0); + assert(setenv("PATH", new_path, 1) == 0); + } + ::testing::InitGoogleTest(&argc, argv); for (int ii = 1; ii < argc; ii++) { if (strcmp(argv[ii], "-v") == 0) { verbose = true; } else if (strcmp(argv[ii], "-T") == 0) { ii++; assert(ii < argc); tmpdir = argv[ii]; struct stat info; stat(tmpdir.c_str(), &info); assert(S_ISDIR(info.st_mode)); } else if (strcmp(argv[ii], "-t") == 0) { force_mt = true; } else if (strcmp(argv[ii], "-F") == 0) { force_nofork = true; } else if (strcmp(argv[ii], "-u") == 0) { if (++ii >= argc) { std::cerr << "-u needs argument" << std::endl; exit(1); } if (isdigit(argv[ii][0])) { other_uid = atoi(argv[ii]); } else { struct passwd *p = getpwnam(argv[ii]); if (!p) { std::cerr << "Failed to get entry for " << argv[ii] << ", errno=" << errno << std::endl; exit(1); } other_uid = p->pw_uid; } } } if (other_uid == 0) { struct stat info; if (stat(argv[0], &info) == 0) { other_uid = info.st_uid; } } #ifdef __linux__ // Check whether our temporary directory is on a tmpfs volume. struct statfs fsinfo; statfs(tmpdir.c_str(), &fsinfo); tmpdir_on_tmpfs = (fsinfo.f_type == TMPFS_MAGIC); #endif testing::AddGlobalTestEnvironment(new SetupEnvironment()); int rc = RUN_ALL_TESTS(); ShowSkippedTests(std::cerr); return rc; } Index: projects/capsicum-test/contrib/capsicum-test/capsicum.h =================================================================== --- projects/capsicum-test/contrib/capsicum-test/capsicum.h (revision 345648) +++ projects/capsicum-test/contrib/capsicum-test/capsicum.h (revision 345649) @@ -1,170 +1,175 @@ /* * Minimal portability layer for Capsicum-related features. */ #ifndef __CAPSICUM_H__ #define __CAPSICUM_H__ #ifdef __FreeBSD__ #include "capsicum-freebsd.h" #endif #ifdef __linux__ #include "capsicum-linux.h" #endif /* * CAP_ALL/CAP_NONE is a value in FreeBSD9.x Capsicum, but a functional macro * in FreeBSD10.x Capsicum. Always use CAP_SET_ALL/CAP_SET_NONE instead. */ #ifndef CAP_SET_ALL #ifdef CAP_RIGHTS_VERSION #define CAP_SET_ALL(rights) CAP_ALL(rights) #else #define CAP_SET_ALL(rights) *(rights) = CAP_MASK_VALID #endif #endif #ifndef CAP_SET_NONE #ifdef CAP_RIGHTS_VERSION #define CAP_SET_NONE(rights) CAP_NONE(rights) #else #define CAP_SET_NONE(rights) *(rights) = 0 #endif #endif /************************************************************ * Define new-style rights in terms of old-style rights if * absent. ************************************************************/ #include "capsicum-rights.h" /* * Cope with systems (e.g. FreeBSD 10.x) where CAP_RENAMEAT hasn't been split out. * (src, dest): RENAMEAT, LINKAT => RENAMEAT_SOURCE, RENAMEAT_TARGET */ #ifndef CAP_RENAMEAT_SOURCE #define CAP_RENAMEAT_SOURCE CAP_RENAMEAT #endif #ifndef CAP_RENAMEAT_TARGET #define CAP_RENAMEAT_TARGET CAP_LINKAT #endif /* * Cope with systems (e.g. FreeBSD 10.x) where CAP_RENAMEAT hasn't been split out. * (src, dest): 0, LINKAT => LINKAT_SOURCE, LINKAT_TARGET */ #ifndef CAP_LINKAT_SOURCE #define CAP_LINKAT_SOURCE CAP_LOOKUP #endif #ifndef CAP_LINKAT_TARGET #define CAP_LINKAT_TARGET CAP_LINKAT #endif #ifdef CAP_PREAD /* Existence of CAP_PREAD implies new-style CAP_SEEK semantics */ #define CAP_SEEK_ASWAS 0 #else /* Old-style CAP_SEEK semantics */ #define CAP_SEEK_ASWAS CAP_SEEK #define CAP_PREAD CAP_READ #define CAP_PWRITE CAP_WRITE #endif #ifndef CAP_MMAP_R #define CAP_MMAP_R (CAP_READ|CAP_MMAP) #define CAP_MMAP_W (CAP_WRITE|CAP_MMAP) #define CAP_MMAP_X (CAP_MAPEXEC|CAP_MMAP) #define CAP_MMAP_RW (CAP_MMAP_R|CAP_MMAP_W) #define CAP_MMAP_RX (CAP_MMAP_R|CAP_MMAP_X) #define CAP_MMAP_WX (CAP_MMAP_W|CAP_MMAP_X) #define CAP_MMAP_RWX (CAP_MMAP_R|CAP_MMAP_W|CAP_MMAP_X) #endif #ifndef CAP_MKFIFOAT #define CAP_MKFIFOAT CAP_MKFIFO #endif #ifndef CAP_MKNODAT #define CAP_MKNODAT CAP_MKFIFOAT #endif #ifndef CAP_MKDIRAT #define CAP_MKDIRAT CAP_MKDIR #endif #ifndef CAP_UNLINKAT #define CAP_UNLINKAT CAP_RMDIR #endif #ifndef CAP_SOCK_CLIENT #define CAP_SOCK_CLIENT \ (CAP_CONNECT | CAP_GETPEERNAME | CAP_GETSOCKNAME | CAP_GETSOCKOPT | \ CAP_PEELOFF | CAP_READ | CAP_WRITE | CAP_SETSOCKOPT | CAP_SHUTDOWN) #endif #ifndef CAP_SOCK_SERVER #define CAP_SOCK_SERVER \ (CAP_ACCEPT | CAP_BIND | CAP_GETPEERNAME | CAP_GETSOCKNAME | \ CAP_GETSOCKOPT | CAP_LISTEN | CAP_PEELOFF | CAP_READ | CAP_WRITE | \ CAP_SETSOCKOPT | CAP_SHUTDOWN) #endif #ifndef CAP_EVENT #define CAP_EVENT CAP_POLL_EVENT #endif /************************************************************ * Define new-style API functions in terms of old-style API * functions if absent. ************************************************************/ #ifndef HAVE_CAP_RIGHTS_GET /* Define cap_rights_get() in terms of old-style cap_getrights() */ inline int cap_rights_get(int fd, cap_rights_t *rights) { return cap_getrights(fd, rights); } #endif #ifndef HAVE_CAP_RIGHTS_LIMIT /* Define cap_rights_limit() in terms of old-style cap_new() and dup2() */ #include inline int cap_rights_limit(int fd, const cap_rights_t *rights) { int cap = cap_new(fd, *rights); if (cap < 0) return cap; int rc = dup2(cap, fd); if (rc < 0) return rc; close(cap); return rc; } #endif #include #ifdef CAP_RIGHTS_VERSION /* New-style Capsicum API extras for debugging */ static inline void cap_rights_describe(const cap_rights_t *rights, char *buffer) { int ii; for (ii = 0; ii < (CAP_RIGHTS_VERSION+2); ii++) { int len = sprintf(buffer, "0x%016llx ", (unsigned long long)rights->cr_rights[ii]); buffer += len; } } #ifdef __cplusplus #include #include inline std::ostream& operator<<(std::ostream& os, cap_rights_t rights) { for (int ii = 0; ii < (CAP_RIGHTS_VERSION+2); ii++) { os << std::hex << std::setw(16) << std::setfill('0') << (unsigned long long)rights.cr_rights[ii] << " "; } return os; } #endif #else static inline void cap_rights_describe(const cap_rights_t *rights, char *buffer) { sprintf(buffer, "0x%016llx", (*rights)); } #endif /* new/old style rights manipulation */ +#ifdef __cplusplus +#include +extern std::string capsicum_test_bindir; +#endif + #endif /*__CAPSICUM_H__*/ Index: projects/capsicum-test/contrib/capsicum-test/fexecve.cc =================================================================== --- projects/capsicum-test/contrib/capsicum-test/fexecve.cc (revision 345648) +++ projects/capsicum-test/contrib/capsicum-test/fexecve.cc (revision 345649) @@ -1,173 +1,203 @@ -#include -#include #include #include #include +#include #include -#include #include #include +#include +#include #include #include "syscalls.h" #include "capsicum.h" #include "capsicum-test.h" -// We need a program to exec(), but for fexecve() to work in capability -// mode that program needs to be statically linked (otherwise ld.so will -// attempt to traverse the filesystem to load (e.g.) /lib/libc.so and -// fail). -#define EXEC_PROG "./mini-me" -#define EXEC_PROG_NOEXEC EXEC_PROG ".noexec" -#define EXEC_PROG_SETUID EXEC_PROG ".setuid" - // Arguments to use in execve() calls. -static char* argv_pass[] = {(char*)EXEC_PROG, (char*)"--pass", NULL}; -static char* argv_fail[] = {(char*)EXEC_PROG, (char*)"--fail", NULL}; -static char* argv_checkroot[] = {(char*)EXEC_PROG, (char*)"--checkroot", NULL}; static char* null_envp[] = {NULL}; class Execve : public ::testing::Test { public: - Execve() : exec_fd_(open(EXEC_PROG, O_RDONLY)) { + Execve() : exec_fd_(-1) { + // We need a program to exec(), but for fexecve() to work in capability + // mode that program needs to be statically linked (otherwise ld.so will + // attempt to traverse the filesystem to load (e.g.) /lib/libc.so and + // fail). + exec_prog_ = capsicum_test_bindir + "/mini-me"; + exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec"; + exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid"; + + exec_fd_ = open(exec_prog_.c_str(), O_RDONLY); if (exec_fd_ < 0) { - fprintf(stderr, "Error! Failed to open %s\n", EXEC_PROG); + fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str()); } + argv_checkroot_[0] = (char*)exec_prog_.c_str(); + argv_fail_[0] = (char*)exec_prog_.c_str(); + argv_pass_[0] = (char*)exec_prog_.c_str(); } - ~Execve() { if (exec_fd_ >= 0) close(exec_fd_); } + ~Execve() { + if (exec_fd_ >= 0) { + close(exec_fd_); + exec_fd_ = -1; + } + } protected: + char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr}; + char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr}; + char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr}; + std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_; int exec_fd_; }; +class Fexecve : public Execve { + public: + Fexecve() : Execve() {} +}; + +class FexecveWithScript : public Fexecve { + public: + FexecveWithScript() : + Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {} + + void SetUp() override { + // First, build an executable shell script + int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755); + EXPECT_OK(fd); + const char* contents = "#!/bin/sh\nexit 99\n"; + EXPECT_OK(write(fd, contents, strlen(contents))); + close(fd); + } + void TearDown() override { + (void)::unlink(temp_script_filename_); + } + + const char *temp_script_filename_; +}; + FORK_TEST_F(Execve, BasicFexecve) { - EXPECT_OK(fexecve_(exec_fd_, argv_pass, null_envp)); + EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } FORK_TEST_F(Execve, InCapMode) { EXPECT_OK(cap_enter()); - EXPECT_OK(fexecve_(exec_fd_, argv_pass, null_envp)); + EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } FORK_TEST_F(Execve, FailWithoutCap) { EXPECT_OK(cap_enter()); int cap_fd = dup(exec_fd_); EXPECT_OK(cap_fd); cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); - EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail, null_envp)); + EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp)); EXPECT_EQ(ENOTCAPABLE, errno); } FORK_TEST_F(Execve, SucceedWithCap) { EXPECT_OK(cap_enter()); int cap_fd = dup(exec_fd_); EXPECT_OK(cap_fd); cap_rights_t rights; // TODO(drysdale): would prefer that Linux Capsicum not need all of these // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); - EXPECT_OK(fexecve_(cap_fd, argv_pass, null_envp)); + EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); } -FORK_TEST(Fexecve, ExecutePermissionCheck) { - int fd = open(EXEC_PROG_NOEXEC, O_RDONLY); +FORK_TEST_F(Fexecve, ExecutePermissionCheck) { + int fd = open(exec_prog_noexec_.c_str(), O_RDONLY); EXPECT_OK(fd); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); - EXPECT_EQ(-1, fexecve_(fd, argv_fail, null_envp)); + EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp)); EXPECT_EQ(EACCES, errno); close(fd); } } -FORK_TEST(Fexecve, SetuidIgnored) { +FORK_TEST_F(Fexecve, SetuidIgnored) { if (geteuid() == 0) { TEST_SKIPPED("requires non-root"); return; } - int fd = open(EXEC_PROG_SETUID, O_RDONLY); + int fd = open(exec_prog_setuid_.c_str(), O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_enter()); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID); - EXPECT_OK(fexecve_(fd, argv_checkroot, null_envp)); + EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); close(fd); } } -FORK_TEST(Fexecve, ExecveFailure) { +FORK_TEST_F(Fexecve, ExecveFailure) { EXPECT_OK(cap_enter()); - EXPECT_EQ(-1, execve(argv_fail[0], argv_fail, null_envp)); + EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp)); EXPECT_EQ(ECAPMODE, errno); } -FORK_TEST_ON(Fexecve, CapModeScriptFail, TmpFile("cap_sh_script")) { - // First, build an executable shell script - int fd = open(TmpFile("cap_sh_script"), O_RDWR|O_CREAT, 0755); - EXPECT_OK(fd); - const char* contents = "#!/bin/sh\nexit 99\n"; - EXPECT_OK(write(fd, contents, strlen(contents))); - close(fd); +FORK_TEST_F(FexecveWithScript, CapModeScriptFail) { + int fd; // Open the script file, with CAP_FEXECVE rights. - fd = open(TmpFile("cap_sh_script"), O_RDONLY); + fd = open(temp_script_filename_, O_RDONLY); cap_rights_t rights; cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); EXPECT_OK(cap_rights_limit(fd, &rights)); EXPECT_OK(cap_enter()); // Enter capability mode // Attempt fexecve; should fail, because "/bin/sh" is inaccessible. - EXPECT_EQ(-1, fexecve_(fd, argv_pass, null_envp)); + EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp)); } #ifdef HAVE_EXECVEAT TEST(Execveat, NoUpwardTraversal) { - char *abspath = realpath(EXEC_PROG, NULL); + char *abspath = realpath(exec_prog_, NULL); char cwd[1024]; getcwd(cwd, sizeof(cwd)); int dfd = open(".", O_DIRECTORY|O_RDONLY); pid_t child = fork(); if (child == 0) { EXPECT_OK(cap_enter()); // Enter capability mode. // Can't execveat() an absolute path, even relative to a dfd. EXPECT_SYSCALL_FAIL(ECAPMODE, - execveat(AT_FDCWD, abspath, argv_pass, null_envp, 0)); + execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0)); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, - execveat(dfd, abspath, argv_pass, null_envp, 0)); + execveat(dfd, abspath, argv_pass_, null_envp, 0)); // Can't execveat() a relative path ("..//./"). char *p = cwd + strlen(cwd); while (*p != '/') p--; char buffer[1024] = "../"; strcat(buffer, ++p); strcat(buffer, "/"); - strcat(buffer, EXEC_PROG); + strcat(buffer, exec_prog_); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, - execveat(dfd, buffer, argv_pass, null_envp, 0)); + execveat(dfd, buffer, argv_pass_, null_envp, 0)); exit(HasFailure() ? 99 : 123); } int status; EXPECT_EQ(child, waitpid(child, &status, 0)); EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; EXPECT_EQ(123, WEXITSTATUS(status)); free(abspath); close(dfd); } #endif