xref: /freebsd/contrib/capsicum-test/fexecve.cc (revision b9f654b163bce26de79705e77b872427c9f2afa1)
1 #include <sys/types.h>
2 #include <sys/wait.h>
3 #include <sys/stat.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, SetuidIgnored) {
130   if (geteuid() == 0) {
131     TEST_SKIPPED("requires non-root");
132     return;
133   }
134   int fd = open(exec_prog_setuid_.c_str(), O_RDONLY);
135   EXPECT_OK(fd);
136   EXPECT_OK(cap_enter());
137   if (fd >= 0) {
138     struct stat data;
139     EXPECT_OK(fstat(fd, &data));
140     EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID);
141     EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp));
142     // Should not reach here, exec() takes over.
143     EXPECT_TRUE(!"fexecve() should have succeeded");
144     close(fd);
145   }
146 }
147 
148 FORK_TEST_F(Fexecve, ExecveFailure) {
149   EXPECT_OK(cap_enter());
150   EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp));
151   EXPECT_EQ(ECAPMODE, errno);
152 }
153 
154 FORK_TEST_F(FexecveWithScript, CapModeScriptFail) {
155   int fd;
156 
157   // Open the script file, with CAP_FEXECVE rights.
158   fd = open(temp_script_filename_, O_RDONLY);
159   cap_rights_t rights;
160   cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK);
161   EXPECT_OK(cap_rights_limit(fd, &rights));
162 
163   EXPECT_OK(cap_enter());  // Enter capability mode
164 
165   // Attempt fexecve; should fail, because "/bin/sh" is inaccessible.
166   EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp));
167 }
168 
169 #ifdef HAVE_EXECVEAT
170 class Execveat : public Execve {
171  public:
172   Execveat() : Execve() {}
173 };
174 
175 TEST_F(Execveat, NoUpwardTraversal) {
176   char *abspath = realpath(exec_prog_, NULL);
177   char cwd[1024];
178   getcwd(cwd, sizeof(cwd));
179 
180   int dfd = open(".", O_DIRECTORY|O_RDONLY);
181   pid_t child = fork();
182   if (child == 0) {
183     EXPECT_OK(cap_enter());  // Enter capability mode.
184     // Can't execveat() an absolute path, even relative to a dfd.
185     EXPECT_SYSCALL_FAIL(ECAPMODE,
186                         execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0));
187     EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
188                         execveat(dfd, abspath, argv_pass_, null_envp, 0));
189 
190     // Can't execveat() a relative path ("../<dir>/./<exe>").
191     char *p = cwd + strlen(cwd);
192     while (*p != '/') p--;
193     char buffer[1024] = "../";
194     strcat(buffer, ++p);
195     strcat(buffer, "/");
196     strcat(buffer, exec_prog_);
197     EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
198                         execveat(dfd, buffer, argv_pass_, null_envp, 0));
199     exit(HasFailure() ? 99 : 123);
200   }
201   int status;
202   EXPECT_EQ(child, waitpid(child, &status, 0));
203   EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status;
204   EXPECT_EQ(123, WEXITSTATUS(status));
205   free(abspath);
206   close(dfd);
207 }
208 #endif
209