Changeset View
Changeset View
Standalone View
Standalone View
contrib/capsicum-test/fexecve.cc
#include <errno.h> | |||||
#include <string.h> | |||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <sys/wait.h> | #include <sys/wait.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <errno.h> | |||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <unistd.h> | |||||
#include <limits.h> | #include <limits.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include <sstream> | #include <sstream> | ||||
#include "syscalls.h" | #include "syscalls.h" | ||||
#include "capsicum.h" | #include "capsicum.h" | ||||
#include "capsicum-test.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. | // 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}; | static char* null_envp[] = {NULL}; | ||||
class Execve : public ::testing::Test { | class Execve : public ::testing::Test { | ||||
public: | 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) { | 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: | 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_; | 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) { | 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. | // Should not reach here, exec() takes over. | ||||
EXPECT_TRUE(!"fexecve() should never return"); | EXPECT_TRUE(!"fexecve() should never return"); | ||||
} | } | ||||
FORK_TEST_F(Execve, InCapMode) { | FORK_TEST_F(Execve, InCapMode) { | ||||
EXPECT_OK(cap_enter()); | 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. | // Should not reach here, exec() takes over. | ||||
EXPECT_TRUE(!"fexecve() should never return"); | EXPECT_TRUE(!"fexecve() should never return"); | ||||
} | } | ||||
FORK_TEST_F(Execve, FailWithoutCap) { | FORK_TEST_F(Execve, FailWithoutCap) { | ||||
EXPECT_OK(cap_enter()); | EXPECT_OK(cap_enter()); | ||||
int cap_fd = dup(exec_fd_); | int cap_fd = dup(exec_fd_); | ||||
EXPECT_OK(cap_fd); | EXPECT_OK(cap_fd); | ||||
cap_rights_t rights; | cap_rights_t rights; | ||||
cap_rights_init(&rights, 0); | cap_rights_init(&rights, 0); | ||||
EXPECT_OK(cap_rights_limit(cap_fd, &rights)); | 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); | EXPECT_EQ(ENOTCAPABLE, errno); | ||||
} | } | ||||
FORK_TEST_F(Execve, SucceedWithCap) { | FORK_TEST_F(Execve, SucceedWithCap) { | ||||
EXPECT_OK(cap_enter()); | EXPECT_OK(cap_enter()); | ||||
int cap_fd = dup(exec_fd_); | int cap_fd = dup(exec_fd_); | ||||
EXPECT_OK(cap_fd); | EXPECT_OK(cap_fd); | ||||
cap_rights_t rights; | cap_rights_t rights; | ||||
// TODO(drysdale): would prefer that Linux Capsicum not need all of these | // TODO(drysdale): would prefer that Linux Capsicum not need all of these | ||||
// rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. | // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. | ||||
cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); | cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); | ||||
EXPECT_OK(cap_rights_limit(cap_fd, &rights)); | 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. | // Should not reach here, exec() takes over. | ||||
EXPECT_TRUE(!"fexecve() should have succeeded"); | EXPECT_TRUE(!"fexecve() should have succeeded"); | ||||
} | } | ||||
FORK_TEST(Fexecve, ExecutePermissionCheck) { | FORK_TEST_F(Fexecve, ExecutePermissionCheck) { | ||||
int fd = open(EXEC_PROG_NOEXEC, O_RDONLY); | int fd = open(exec_prog_noexec_.c_str(), O_RDONLY); | ||||
EXPECT_OK(fd); | EXPECT_OK(fd); | ||||
if (fd >= 0) { | if (fd >= 0) { | ||||
struct stat data; | struct stat data; | ||||
EXPECT_OK(fstat(fd, &data)); | EXPECT_OK(fstat(fd, &data)); | ||||
EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); | 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); | EXPECT_EQ(EACCES, errno); | ||||
close(fd); | close(fd); | ||||
} | } | ||||
} | } | ||||
FORK_TEST(Fexecve, SetuidIgnored) { | FORK_TEST_F(Fexecve, SetuidIgnored) { | ||||
if (geteuid() == 0) { | if (geteuid() == 0) { | ||||
TEST_SKIPPED("requires non-root"); | TEST_SKIPPED("requires non-root"); | ||||
return; | return; | ||||
} | } | ||||
int fd = open(EXEC_PROG_SETUID, O_RDONLY); | int fd = open(exec_prog_setuid_.c_str(), O_RDONLY); | ||||
EXPECT_OK(fd); | EXPECT_OK(fd); | ||||
EXPECT_OK(cap_enter()); | EXPECT_OK(cap_enter()); | ||||
if (fd >= 0) { | if (fd >= 0) { | ||||
struct stat data; | struct stat data; | ||||
EXPECT_OK(fstat(fd, &data)); | EXPECT_OK(fstat(fd, &data)); | ||||
EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID); | 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. | // Should not reach here, exec() takes over. | ||||
EXPECT_TRUE(!"fexecve() should have succeeded"); | EXPECT_TRUE(!"fexecve() should have succeeded"); | ||||
close(fd); | close(fd); | ||||
} | } | ||||
} | } | ||||
FORK_TEST(Fexecve, ExecveFailure) { | FORK_TEST_F(Fexecve, ExecveFailure) { | ||||
EXPECT_OK(cap_enter()); | 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); | EXPECT_EQ(ECAPMODE, errno); | ||||
} | } | ||||
FORK_TEST_ON(Fexecve, CapModeScriptFail, TmpFile("cap_sh_script")) { | FORK_TEST_F(FexecveWithScript, CapModeScriptFail) { | ||||
// First, build an executable shell script | int fd; | ||||
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); | |||||
// Open the script file, with CAP_FEXECVE rights. | // 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_t rights; | ||||
cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); | cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); | ||||
EXPECT_OK(cap_rights_limit(fd, &rights)); | EXPECT_OK(cap_rights_limit(fd, &rights)); | ||||
EXPECT_OK(cap_enter()); // Enter capability mode | EXPECT_OK(cap_enter()); // Enter capability mode | ||||
// Attempt fexecve; should fail, because "/bin/sh" is inaccessible. | // 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 | #ifdef HAVE_EXECVEAT | ||||
TEST(Execveat, NoUpwardTraversal) { | TEST(Execveat, NoUpwardTraversal) { | ||||
char *abspath = realpath(EXEC_PROG, NULL); | char *abspath = realpath(exec_prog_, NULL); | ||||
char cwd[1024]; | char cwd[1024]; | ||||
getcwd(cwd, sizeof(cwd)); | getcwd(cwd, sizeof(cwd)); | ||||
int dfd = open(".", O_DIRECTORY|O_RDONLY); | int dfd = open(".", O_DIRECTORY|O_RDONLY); | ||||
pid_t child = fork(); | pid_t child = fork(); | ||||
if (child == 0) { | if (child == 0) { | ||||
EXPECT_OK(cap_enter()); // Enter capability mode. | EXPECT_OK(cap_enter()); // Enter capability mode. | ||||
// Can't execveat() an absolute path, even relative to a dfd. | // Can't execveat() an absolute path, even relative to a dfd. | ||||
EXPECT_SYSCALL_FAIL(ECAPMODE, | 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, | 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>"). | // Can't execveat() a relative path ("../<dir>/./<exe>"). | ||||
char *p = cwd + strlen(cwd); | char *p = cwd + strlen(cwd); | ||||
while (*p != '/') p--; | while (*p != '/') p--; | ||||
char buffer[1024] = "../"; | char buffer[1024] = "../"; | ||||
strcat(buffer, ++p); | strcat(buffer, ++p); | ||||
strcat(buffer, "/"); | strcat(buffer, "/"); | ||||
strcat(buffer, EXEC_PROG); | strcat(buffer, exec_prog_); | ||||
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, | 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); | exit(HasFailure() ? 99 : 123); | ||||
} | } | ||||
int status; | int status; | ||||
EXPECT_EQ(child, waitpid(child, &status, 0)); | EXPECT_EQ(child, waitpid(child, &status, 0)); | ||||
EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; | EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; | ||||
EXPECT_EQ(123, WEXITSTATUS(status)); | EXPECT_EQ(123, WEXITSTATUS(status)); | ||||
free(abspath); | free(abspath); | ||||
close(dfd); | close(dfd); | ||||
} | } | ||||
#endif | #endif |