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.
18static char* null_envp[] = {NULL};
19
20class 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  }
45protected:
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
53class Fexecve : public Execve {
54 public:
55  Fexecve() : Execve() {}
56};
57
58class 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
78FORK_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
84FORK_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
91FORK_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
102FORK_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
116FORK_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
129FORK_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
147FORK_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
153FORK_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
169class Execveat : public Execve {
170 public:
171  Execveat() : Execve() {}
172};
173
174TEST_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