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