1#include <sys/types.h>
2#include <sys/stat.h>
3#include <fcntl.h>
4#include <sys/ioctl.h>
5
6#include <string>
7
8#include "capsicum.h"
9#include "capsicum-test.h"
10#include "syscalls.h"
11
12// Check an open call works and close the resulting fd.
13#define EXPECT_OPEN_OK(f) do { \
14    SCOPED_TRACE(#f);          \
15    int _fd = f;               \
16    EXPECT_OK(_fd);            \
17    close(_fd);                \
18  } while (0)
19
20static void CreateFile(const char *filename, const char *contents) {
21  int fd = open(filename, O_CREAT|O_RDWR, 0644);
22  EXPECT_OK(fd);
23  EXPECT_OK(write(fd, contents, strlen(contents)));
24  close(fd);
25}
26
27// Test openat(2) in a variety of sitations to ensure that it obeys Capsicum
28// "strict relative" rules:
29//
30// 1. Use strict relative lookups in capability mode or when operating
31//    relative to a capability.
32// 2. When performing strict relative lookups, absolute paths (including
33//    symlinks to absolute paths) are not allowed, nor are paths containing
34//    '..' components.
35//
36// These rules apply when:
37//  - the directory FD is a Capsicum capability
38//  - the process is in capability mode
39//  - the openat(2) operation includes the O_BENEATH flag.
40FORK_TEST(Openat, Relative) {
41  int etc = open("/etc/", O_RDONLY);
42  EXPECT_OK(etc);
43
44  cap_rights_t r_base;
45  cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL);
46  cap_rights_t r_ro;
47  cap_rights_init(&r_ro, CAP_READ);
48  cap_rights_t r_rl;
49  cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP);
50
51  int etc_cap = dup(etc);
52  EXPECT_OK(etc_cap);
53  EXPECT_OK(cap_rights_limit(etc_cap, &r_ro));
54  int etc_cap_ro = dup(etc);
55  EXPECT_OK(etc_cap_ro);
56  EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl));
57  int etc_cap_base = dup(etc);
58  EXPECT_OK(etc_cap_base);
59  EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base));
60#ifdef HAVE_CAP_FCNTLS_LIMIT
61  // Also limit fcntl(2) subrights.
62  EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL));
63#endif
64#ifdef HAVE_CAP_IOCTLS_LIMIT
65  // Also limit ioctl(2) subrights.
66  cap_ioctl_t ioctl_nread = FIONREAD;
67  EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1));
68#endif
69
70  // openat(2) with regular file descriptors in non-capability mode
71  // Should Just Work (tm).
72  EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY));
73  EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
74  EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY));
75  EXPECT_OPEN_OK(openat(etc, "../etc/passwd", O_RDONLY));
76
77  // Lookups relative to capabilities should be strictly relative.
78  // When not in capability mode, we don't actually require CAP_LOOKUP.
79  EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY));
80  EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY));
81
82  // Performing openat(2) on a path with leading slash ignores
83  // the provided directory FD.
84  EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY));
85  EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY));
86  // Relative lookups that go upward are not allowed.
87  EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY);
88  EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY);
89
90  // A file opened relative to a capability should itself be a capability.
91  int fd = openat(etc_cap_base, "passwd", O_RDONLY);
92  EXPECT_OK(fd);
93  cap_rights_t rights;
94  EXPECT_OK(cap_rights_get(fd, &rights));
95  EXPECT_RIGHTS_IN(&rights, &r_base);
96#ifdef HAVE_CAP_FCNTLS_LIMIT
97  cap_fcntl_t fcntls;
98  EXPECT_OK(cap_fcntls_get(fd, &fcntls));
99  EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
100#endif
101#ifdef HAVE_CAP_IOCTLS_LIMIT
102  cap_ioctl_t ioctls[16];
103  ssize_t nioctls;
104  memset(ioctls, 0, sizeof(ioctls));
105  nioctls = cap_ioctls_get(fd, ioctls, 16);
106  EXPECT_OK(nioctls);
107  EXPECT_EQ(1, nioctls);
108  EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
109#endif
110  close(fd);
111
112  // Enter capability mode; now ALL lookups are strictly relative.
113  EXPECT_OK(cap_enter());
114
115  // Relative lookups on regular files or capabilities with CAP_LOOKUP
116  // ought to succeed.
117  EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY));
118  EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY));
119  EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY));
120
121  // Lookup relative to capabilities without CAP_LOOKUP should fail.
122  EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY));
123
124  // Absolute lookups should fail.
125  EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
126  EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY);
127  EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY);
128
129  // Lookups containing '..' should fail in capability mode.
130  EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY);
131  EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY);
132  EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY);
133
134  fd = openat(etc, "passwd", O_RDONLY);
135  EXPECT_OK(fd);
136
137  // A file opened relative to a capability should itself be a capability.
138  fd = openat(etc_cap_base, "passwd", O_RDONLY);
139  EXPECT_OK(fd);
140  EXPECT_OK(cap_rights_get(fd, &rights));
141  EXPECT_RIGHTS_IN(&rights, &r_base);
142  close(fd);
143
144  fd = openat(etc_cap_ro, "passwd", O_RDONLY);
145  EXPECT_OK(fd);
146  EXPECT_OK(cap_rights_get(fd, &rights));
147  EXPECT_RIGHTS_IN(&rights, &r_rl);
148  close(fd);
149}
150
151#define TOPDIR "cap_topdir"
152#define SUBDIR TOPDIR "/subdir"
153class OpenatTest : public ::testing::Test {
154 public:
155  // Build a collection of files, subdirs and symlinks:
156  //  /tmp/cap_topdir/
157  //                 /topfile
158  //                 /subdir/
159  //                 /subdir/bottomfile
160  //                 /symlink.samedir              -> topfile
161  //                 /dsymlink.samedir             -> ./
162  //                 /symlink.down                 -> subdir/bottomfile
163  //                 /dsymlink.down                -> subdir/
164  //                 /symlink.absolute_out         -> /etc/passwd
165  //                 /dsymlink.absolute_out        -> /etc/
166  //                 /symlink.relative_in          -> ../../tmp/cap_topdir/topfile
167  //                 /dsymlink.relative_in         -> ../../tmp/cap_topdir/
168  //                 /symlink.relative_out         -> ../../etc/passwd
169  //                 /dsymlink.relative_out        -> ../../etc/
170  //                 /subdir/dsymlink.absolute_in  -> /tmp/cap_topdir/
171  //                 /subdir/dsymlink.up           -> ../
172  //                 /subdir/symlink.absolute_in   -> /tmp/cap_topdir/topfile
173  //                 /subdir/symlink.up            -> ../topfile
174  // (In practice, this is a little more complicated because tmpdir might
175  // not be "/tmp".)
176  OpenatTest() {
177    // Create a couple of nested directories
178    int rc = mkdir(TmpFile(TOPDIR), 0755);
179    EXPECT_OK(rc);
180    if (rc < 0) {
181      EXPECT_EQ(EEXIST, errno);
182    }
183    rc = mkdir(TmpFile(SUBDIR), 0755);
184    EXPECT_OK(rc);
185    if (rc < 0) {
186      EXPECT_EQ(EEXIST, errno);
187    }
188
189    // Figure out a path prefix (like "../..") that gets us to the root
190    // directory from TmpFile(TOPDIR).
191    const char *p = TmpFile(TOPDIR);  // maybe "/tmp/somewhere/cap_topdir"
192    std::string dots2root = "..";
193    while (*p++ != '\0') {
194      if (*p == '/') {
195        dots2root += "/..";
196      }
197    }
198
199    // Create normal files in each.
200    CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file");
201    CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory");
202
203    // Create various symlinks to files.
204    EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir")));
205    EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down")));
206    EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(SUBDIR "/symlink.absolute_in")));
207    EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out")));
208    std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile");
209    EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in")));
210    std::string dots2passwd = dots2root + "/etc/passwd";
211    EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out")));
212    EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR "/symlink.up")));
213
214    // Create various symlinks to directories.
215    EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir")));
216    EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down")));
217    EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(SUBDIR "/dsymlink.absolute_in")));
218    EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out")));
219    std::string dots2cwd = dots2root + tmpdir + "/";
220    EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in")));
221    std::string dots2etc = dots2root + "/etc/";
222    EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out")));
223    EXPECT_OK(symlink("../", TmpFile(SUBDIR "/dsymlink.up")));
224
225    // Open directory FDs for those directories and for cwd.
226    dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY);
227    EXPECT_OK(dir_fd_);
228    sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY);
229    EXPECT_OK(sub_fd_);
230    cwd_ = openat(AT_FDCWD, ".", O_RDONLY);
231    EXPECT_OK(cwd_);
232    // Move into the directory for the test.
233    EXPECT_OK(fchdir(dir_fd_));
234  }
235  ~OpenatTest() {
236    fchdir(cwd_);
237    close(cwd_);
238    close(sub_fd_);
239    close(dir_fd_);
240    unlink(TmpFile(SUBDIR "/symlink.up"));
241    unlink(TmpFile(SUBDIR "/symlink.absolute_in"));
242    unlink(TmpFile(TOPDIR "/symlink.absolute_out"));
243    unlink(TmpFile(TOPDIR "/symlink.relative_in"));
244    unlink(TmpFile(TOPDIR "/symlink.relative_out"));
245    unlink(TmpFile(TOPDIR "/symlink.down"));
246    unlink(TmpFile(TOPDIR "/symlink.samedir"));
247    unlink(TmpFile(SUBDIR "/dsymlink.up"));
248    unlink(TmpFile(SUBDIR "/dsymlink.absolute_in"));
249    unlink(TmpFile(TOPDIR "/dsymlink.absolute_out"));
250    unlink(TmpFile(TOPDIR "/dsymlink.relative_in"));
251    unlink(TmpFile(TOPDIR "/dsymlink.relative_out"));
252    unlink(TmpFile(TOPDIR "/dsymlink.down"));
253    unlink(TmpFile(TOPDIR "/dsymlink.samedir"));
254    unlink(TmpFile(SUBDIR "/bottomfile"));
255    unlink(TmpFile(TOPDIR "/topfile"));
256    rmdir(TmpFile(SUBDIR));
257    rmdir(TmpFile(TOPDIR));
258  }
259
260  // Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH.
261  void CheckPolicing(int oflag) {
262    // OK for normal access.
263    EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag));
264    EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag));
265    EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag));
266    EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag));
267
268    // Can't open paths with ".." in them.
269    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag);
270    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag);
271    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag);
272
273#ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT
274    // OK for dotdot lookups that don't escape the top directory
275    EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag));
276#endif
277
278    // Check that we can't escape the top directory by the cunning
279    // ruse of going via a subdirectory.
280    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag);
281
282    // Should only be able to open symlinks that stay within the directory.
283    EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag));
284    EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag));
285    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag);
286    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag);
287    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag);
288    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.absolute_in", O_RDONLY|oflag);
289    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag);
290
291    EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag));
292    EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag));
293    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag);
294    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag);
295    EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag);
296    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag);
297    EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag);
298
299    // Although recall that O_NOFOLLOW prevents symlink following in final component.
300    EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag));
301    EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag));
302  }
303
304 protected:
305  int dir_fd_;
306  int sub_fd_;
307  int cwd_;
308};
309
310TEST_F(OpenatTest, WithCapability) {
311  // Any kind of symlink can be opened relative to an ordinary directory FD.
312  EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY));
313  EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY));
314  EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY));
315  EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY));
316  EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY));
317  EXPECT_OPEN_OK(openat(sub_fd_, "symlink.absolute_in", O_RDONLY));
318  EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY));
319
320  // Now make both DFDs into Capsicum capabilities.
321  cap_rights_t r_rl;
322  cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR);
323  EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl));
324  EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl));
325  CheckPolicing(0);
326  // Use of AT_FDCWD is independent of use of a capability.
327  // Can open paths starting with "/" against a capability dfd, because the dfd is ignored.
328}
329
330FORK_TEST_F(OpenatTest, InCapabilityMode) {
331  EXPECT_OK(cap_enter());  // Enter capability mode
332  CheckPolicing(0);
333
334  // Use of AT_FDCWD is banned in capability mode.
335  EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY));
336  EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY));
337  EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
338
339  // Can't open paths starting with "/" in capability mode.
340  EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY);
341  EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY);
342}
343
344#if !defined(O_RESOLVE_BENEATH) && defined(O_BENEATH)
345#define O_RESOLVE_BENEATH O_BENEATH
346#endif
347
348#ifdef O_RESOLVE_BENEATH
349TEST_F(OpenatTest, WithFlag) {
350  CheckPolicing(O_RESOLVE_BENEATH);
351
352  // Check with AT_FDCWD.
353  EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_RESOLVE_BENEATH));
354  EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_RESOLVE_BENEATH));
355
356  // Can't open paths starting with "/" with O_RESOLVE_BENEATH specified.
357  EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
358  EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
359  EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH);
360}
361
362FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) {
363  EXPECT_OK(cap_enter());  // Enter capability mode
364  CheckPolicing(O_RESOLVE_BENEATH);
365}
366#endif
367