aboutsummaryrefslogtreecommitdiff
path: root/contrib/capsicum-test/fexecve.cc
blob: 86df2af0638855bab7f866b17b686fdd73d3f130 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sstream>

#include "syscalls.h"
#include "capsicum.h"
#include "capsicum-test.h"

// Arguments to use in execve() calls.
static char* null_envp[] = {NULL};

class Execve : public ::testing::Test {
 public:
  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) {
      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_);
      exec_fd_ = -1;
    }
  }
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_;
};

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) {
  EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
  // Should not reach here, exec() takes over.
  EXPECT_TRUE(!"fexecve() should never return");
}

FORK_TEST_F(Execve, InCapMode) {
  EXPECT_OK(cap_enter());
  EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
  // Should not reach here, exec() takes over.
  EXPECT_TRUE(!"fexecve() should never return");
}

FORK_TEST_F(Execve, FailWithoutCap) {
  EXPECT_OK(cap_enter());
  int cap_fd = dup(exec_fd_);
  EXPECT_OK(cap_fd);
  cap_rights_t rights;
  cap_rights_init(&rights, 0);
  EXPECT_OK(cap_rights_limit(cap_fd, &rights));
  EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp));
  EXPECT_EQ(ENOTCAPABLE, errno);
}

FORK_TEST_F(Execve, SucceedWithCap) {
  EXPECT_OK(cap_enter());
  int cap_fd = dup(exec_fd_);
  EXPECT_OK(cap_fd);
  cap_rights_t rights;
  // TODO(drysdale): would prefer that Linux Capsicum not need all of these
  // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable.
  cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ);
  EXPECT_OK(cap_rights_limit(cap_fd, &rights));
  EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp));
  // Should not reach here, exec() takes over.
  EXPECT_TRUE(!"fexecve() should have succeeded");
}

FORK_TEST_F(Fexecve, ExecutePermissionCheck) {
  int fd = open(exec_prog_noexec_.c_str(), O_RDONLY);
  EXPECT_OK(fd);
  if (fd >= 0) {
    struct stat data;
    EXPECT_OK(fstat(fd, &data));
    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(EACCES, errno);
    close(fd);
  }
}

FORK_TEST_F(Fexecve, SetuidIgnoredIfNonRoot) {
  if (geteuid() == 0) {
    GTEST_SKIP() << "requires non-root";
  }
  int fd = open(exec_prog_setuid_.c_str(), O_RDONLY);
  EXPECT_OK(fd);
  EXPECT_OK(cap_enter());
  if (fd >= 0) {
    struct stat data;
    EXPECT_OK(fstat(fd, &data));
    EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID);
    EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp));
    // Should not reach here, exec() takes over.
    EXPECT_TRUE(!"fexecve() should have succeeded");
    close(fd);
  }
}

FORK_TEST_F(Fexecve, ExecveFailure) {
  EXPECT_OK(cap_enter());
  EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp));
  EXPECT_EQ(ECAPMODE, errno);
}

FORK_TEST_F(FexecveWithScript, CapModeScriptFail) {
  int fd;

  // Open the script file, with CAP_FEXECVE rights.
  fd = open(temp_script_filename_, O_RDONLY);
  cap_rights_t rights;
  cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK);
  EXPECT_OK(cap_rights_limit(fd, &rights));

  EXPECT_OK(cap_enter());  // Enter capability mode

  // Attempt fexecve; should fail, because "/bin/sh" is inaccessible.
  EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp));
}

#ifdef HAVE_EXECVEAT
class Execveat : public Execve {
 public:
  Execveat() : Execve() {}
};

TEST_F(Execveat, NoUpwardTraversal) {
  char *abspath = realpath(exec_prog_.c_str(), NULL);
  char cwd[1024];
  getcwd(cwd, sizeof(cwd));

  int dfd = open(".", O_DIRECTORY|O_RDONLY);
  pid_t child = fork();
  if (child == 0) {
    EXPECT_OK(cap_enter());  // Enter capability mode.
    // Can't execveat() an absolute path, even relative to a dfd.
    EXPECT_SYSCALL_FAIL(ECAPMODE,
                        execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0));
    EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
                        execveat(dfd, abspath, argv_pass_, null_envp, 0));

    // Can't execveat() a relative path ("../<dir>/./<exe>").
    char *p = cwd + strlen(cwd);
    while (*p != '/') p--;
    char buffer[1024] = "../";
    strcat(buffer, ++p);
    strcat(buffer, "/");
    strcat(buffer, exec_prog_.c_str());
    EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
                        execveat(dfd, buffer, argv_pass_, null_envp, 0));
    exit(HasFailure() ? 99 : 123);
  }
  int status;
  EXPECT_EQ(child, waitpid(child, &status, 0));
  EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status;
  EXPECT_EQ(123, WEXITSTATUS(status));
  free(abspath);
  close(dfd);
}
#endif