diff options
Diffstat (limited to 'fexecve.cc')
-rw-r--r-- | fexecve.cc | 128 |
1 files changed, 81 insertions, 47 deletions
diff --git a/fexecve.cc b/fexecve.cc index d4971320a2b7..86df2af06388 100644 --- a/fexecve.cc +++ b/fexecve.cc @@ -1,12 +1,12 @@ -#include <errno.h> -#include <string.h> #include <sys/types.h> -#include <sys/wait.h> #include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> #include <fcntl.h> -#include <unistd.h> #include <limits.h> #include <stdlib.h> +#include <string.h> +#include <unistd.h> #include <sstream> @@ -14,41 +14,76 @@ #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_); + exec_fd_ = -1; } } - ~Execve() { if (exec_fd_ >= 0) close(exec_fd_); } 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"); } @@ -60,7 +95,7 @@ FORK_TEST_F(Execve, FailWithoutCap) { 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); } @@ -73,59 +108,53 @@ FORK_TEST_F(Execve, SucceedWithCap) { // 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, SetuidIgnoredIfNonRoot) { if (geteuid() == 0) { - TEST_SKIPPED("requires non-root"); - return; + GTEST_SKIP() << "requires non-root"; } - 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)); @@ -133,12 +162,17 @@ FORK_TEST_ON(Fexecve, CapModeScriptFail, TmpFile("cap_sh_script")) { 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); +class Execveat : public Execve { + public: + Execveat() : Execve() {} +}; + +TEST_F(Execveat, NoUpwardTraversal) { + char *abspath = realpath(exec_prog_.c_str(), NULL); char cwd[1024]; getcwd(cwd, sizeof(cwd)); @@ -148,9 +182,9 @@ TEST(Execveat, NoUpwardTraversal) { 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 ("../<dir>/./<exe>"). char *p = cwd + strlen(cwd); @@ -158,9 +192,9 @@ TEST(Execveat, NoUpwardTraversal) { char buffer[1024] = "../"; strcat(buffer, ++p); strcat(buffer, "/"); - strcat(buffer, EXEC_PROG); + strcat(buffer, exec_prog_.c_str()); 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; |