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