1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <errno.h> 5 #include <fcntl.h> 6 #include <limits.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <unistd.h> 10 11 #include <sstream> 12 13 #include "syscalls.h" 14 #include "capsicum.h" 15 #include "capsicum-test.h" 16 17 // Arguments to use in execve() calls. 18 static char* null_envp[] = {NULL}; 19 20 class Execve : public ::testing::Test { 21 public: 22 Execve() : exec_fd_(-1) { 23 // We need a program to exec(), but for fexecve() to work in capability 24 // mode that program needs to be statically linked (otherwise ld.so will 25 // attempt to traverse the filesystem to load (e.g.) /lib/libc.so and 26 // fail). 27 exec_prog_ = capsicum_test_bindir + "/mini-me"; 28 exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec"; 29 exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid"; 30 31 exec_fd_ = open(exec_prog_.c_str(), O_RDONLY); 32 if (exec_fd_ < 0) { 33 fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str()); 34 } 35 argv_checkroot_[0] = (char*)exec_prog_.c_str(); 36 argv_fail_[0] = (char*)exec_prog_.c_str(); 37 argv_pass_[0] = (char*)exec_prog_.c_str(); 38 } 39 ~Execve() { 40 if (exec_fd_ >= 0) { 41 close(exec_fd_); 42 exec_fd_ = -1; 43 } 44 } 45 protected: 46 char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr}; 47 char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr}; 48 char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr}; 49 std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_; 50 int exec_fd_; 51 }; 52 53 class Fexecve : public Execve { 54 public: 55 Fexecve() : Execve() {} 56 }; 57 58 class FexecveWithScript : public Fexecve { 59 public: 60 FexecveWithScript() : 61 Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {} 62 63 void SetUp() override { 64 // First, build an executable shell script 65 int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755); 66 EXPECT_OK(fd); 67 const char* contents = "#!/bin/sh\nexit 99\n"; 68 EXPECT_OK(write(fd, contents, strlen(contents))); 69 close(fd); 70 } 71 void TearDown() override { 72 (void)::unlink(temp_script_filename_); 73 } 74 75 const char *temp_script_filename_; 76 }; 77 78 FORK_TEST_F(Execve, BasicFexecve) { 79 EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); 80 // Should not reach here, exec() takes over. 81 EXPECT_TRUE(!"fexecve() should never return"); 82 } 83 84 FORK_TEST_F(Execve, InCapMode) { 85 EXPECT_OK(cap_enter()); 86 EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); 87 // Should not reach here, exec() takes over. 88 EXPECT_TRUE(!"fexecve() should never return"); 89 } 90 91 FORK_TEST_F(Execve, FailWithoutCap) { 92 EXPECT_OK(cap_enter()); 93 int cap_fd = dup(exec_fd_); 94 EXPECT_OK(cap_fd); 95 cap_rights_t rights; 96 cap_rights_init(&rights, 0); 97 EXPECT_OK(cap_rights_limit(cap_fd, &rights)); 98 EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp)); 99 EXPECT_EQ(ENOTCAPABLE, errno); 100 } 101 102 FORK_TEST_F(Execve, SucceedWithCap) { 103 EXPECT_OK(cap_enter()); 104 int cap_fd = dup(exec_fd_); 105 EXPECT_OK(cap_fd); 106 cap_rights_t rights; 107 // TODO(drysdale): would prefer that Linux Capsicum not need all of these 108 // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. 109 cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); 110 EXPECT_OK(cap_rights_limit(cap_fd, &rights)); 111 EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp)); 112 // Should not reach here, exec() takes over. 113 EXPECT_TRUE(!"fexecve() should have succeeded"); 114 } 115 116 FORK_TEST_F(Fexecve, ExecutePermissionCheck) { 117 int fd = open(exec_prog_noexec_.c_str(), O_RDONLY); 118 EXPECT_OK(fd); 119 if (fd >= 0) { 120 struct stat data; 121 EXPECT_OK(fstat(fd, &data)); 122 EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); 123 EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp)); 124 EXPECT_EQ(EACCES, errno); 125 close(fd); 126 } 127 } 128 129 FORK_TEST_F(Fexecve, SetuidIgnoredIfNonRoot) { 130 if (geteuid() == 0) { 131 GTEST_SKIP() << "requires non-root"; 132 } 133 int fd = open(exec_prog_setuid_.c_str(), O_RDONLY); 134 EXPECT_OK(fd); 135 EXPECT_OK(cap_enter()); 136 if (fd >= 0) { 137 struct stat data; 138 EXPECT_OK(fstat(fd, &data)); 139 EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID); 140 EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp)); 141 // Should not reach here, exec() takes over. 142 EXPECT_TRUE(!"fexecve() should have succeeded"); 143 close(fd); 144 } 145 } 146 147 FORK_TEST_F(Fexecve, ExecveFailure) { 148 EXPECT_OK(cap_enter()); 149 EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp)); 150 EXPECT_EQ(ECAPMODE, errno); 151 } 152 153 FORK_TEST_F(FexecveWithScript, CapModeScriptFail) { 154 int fd; 155 156 // Open the script file, with CAP_FEXECVE rights. 157 fd = open(temp_script_filename_, O_RDONLY); 158 cap_rights_t rights; 159 cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); 160 EXPECT_OK(cap_rights_limit(fd, &rights)); 161 162 EXPECT_OK(cap_enter()); // Enter capability mode 163 164 // Attempt fexecve; should fail, because "/bin/sh" is inaccessible. 165 EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp)); 166 } 167 168 #ifdef HAVE_EXECVEAT 169 class Execveat : public Execve { 170 public: 171 Execveat() : Execve() {} 172 }; 173 174 TEST_F(Execveat, NoUpwardTraversal) { 175 char *abspath = realpath(exec_prog_.c_str(), NULL); 176 char cwd[1024]; 177 getcwd(cwd, sizeof(cwd)); 178 179 int dfd = open(".", O_DIRECTORY|O_RDONLY); 180 pid_t child = fork(); 181 if (child == 0) { 182 EXPECT_OK(cap_enter()); // Enter capability mode. 183 // Can't execveat() an absolute path, even relative to a dfd. 184 EXPECT_SYSCALL_FAIL(ECAPMODE, 185 execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0)); 186 EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, 187 execveat(dfd, abspath, argv_pass_, null_envp, 0)); 188 189 // Can't execveat() a relative path ("../<dir>/./<exe>"). 190 char *p = cwd + strlen(cwd); 191 while (*p != '/') p--; 192 char buffer[1024] = "../"; 193 strcat(buffer, ++p); 194 strcat(buffer, "/"); 195 strcat(buffer, exec_prog_.c_str()); 196 EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, 197 execveat(dfd, buffer, argv_pass_, null_envp, 0)); 198 exit(HasFailure() ? 99 : 123); 199 } 200 int status; 201 EXPECT_EQ(child, waitpid(child, &status, 0)); 202 EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status; 203 EXPECT_EQ(123, WEXITSTATUS(status)); 204 free(abspath); 205 close(dfd); 206 } 207 #endif 208