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:
Execve()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 }
~Execve()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:
Fexecve()55 Fexecve() : Execve() {}
56 };
57
58 class FexecveWithScript : public Fexecve {
59 public:
FexecveWithScript()60 FexecveWithScript() :
61 Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {}
62
SetUp()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 }
TearDown()71 void TearDown() override {
72 (void)::unlink(temp_script_filename_);
73 }
74
75 const char *temp_script_filename_;
76 };
77
FORK_TEST_F(Execve,BasicFexecve)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
FORK_TEST_F(Execve,InCapMode)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
FORK_TEST_F(Execve,FailWithoutCap)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
FORK_TEST_F(Execve,SucceedWithCap)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
FORK_TEST_F(Fexecve,ExecutePermissionCheck)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
FORK_TEST_F(Fexecve,SetuidIgnoredIfNonRoot)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
FORK_TEST_F(Fexecve,ExecveFailure)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
FORK_TEST_F(FexecveWithScript,CapModeScriptFail)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:
Execveat()171 Execveat() : Execve() {}
172 };
173
TEST_F(Execveat,NoUpwardTraversal)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