9 #include "capsicum-test.h"
12 // Check an open call works and close the resulting fd.
13 #define EXPECT_OPEN_OK(f) do { \
20 static void CreateFile(const char *filename, const char *contents) {
21 int fd = open(filename, O_CREAT|O_RDWR, 0644);
23 EXPECT_OK(write(fd, contents, strlen(contents)));
27 // Test openat(2) in a variety of sitations to ensure that it obeys Capsicum
28 // "strict relative" rules:
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
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.
40 FORK_TEST(Openat, Relative) {
41 int etc = open("/etc/", O_RDONLY);
45 cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL);
47 cap_rights_init(&r_ro, CAP_READ);
49 cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP);
51 int etc_cap = dup(etc);
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));
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));
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));
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));
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);
90 // A file opened relative to a capability should itself be a capability.
91 int fd = openat(etc_cap_base, "passwd", O_RDONLY);
94 EXPECT_OK(cap_rights_get(fd, &rights));
95 EXPECT_RIGHTS_IN(&rights, &r_base);
96 #ifdef HAVE_CAP_FCNTLS_LIMIT
98 EXPECT_OK(cap_fcntls_get(fd, &fcntls));
99 EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
101 #ifdef HAVE_CAP_IOCTLS_LIMIT
102 cap_ioctl_t ioctls[16];
104 memset(ioctls, 0, sizeof(ioctls));
105 nioctls = cap_ioctls_get(fd, ioctls, 16);
107 EXPECT_EQ(1, nioctls);
108 EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
112 // Enter capability mode; now ALL lookups are strictly relative.
113 EXPECT_OK(cap_enter());
115 // Relative lookups on regular files or capabilities with CAP_LOOKUP
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));
121 // Lookup relative to capabilities without CAP_LOOKUP should fail.
122 EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY));
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);
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);
134 fd = openat(etc, "passwd", O_RDONLY);
137 // A file opened relative to a capability should itself be a capability.
138 fd = openat(etc_cap_base, "passwd", O_RDONLY);
140 EXPECT_OK(cap_rights_get(fd, &rights));
141 EXPECT_RIGHTS_IN(&rights, &r_base);
144 fd = openat(etc_cap_ro, "passwd", O_RDONLY);
146 EXPECT_OK(cap_rights_get(fd, &rights));
147 EXPECT_RIGHTS_IN(&rights, &r_rl);
151 #define TOPDIR "cap_topdir"
152 #define SUBDIR TOPDIR "/subdir"
153 class OpenatTest : public ::testing::Test {
155 // Build a collection of files, subdirs and symlinks:
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
177 // Create a couple of nested directories
178 int rc = mkdir(TmpFile(TOPDIR), 0755);
181 EXPECT_EQ(EEXIST, errno);
183 rc = mkdir(TmpFile(SUBDIR), 0755);
186 EXPECT_EQ(EEXIST, errno);
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') {
199 // Create normal files in each.
200 CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file");
201 CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory");
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")));
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")));
225 // Open directory FDs for those directories and for cwd.
226 dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY);
228 sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY);
230 cwd_ = openat(AT_FDCWD, ".", O_RDONLY);
232 // Move into the directory for the test.
233 EXPECT_OK(fchdir(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));
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));
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);
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));
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);
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);
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);
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));
310 TEST_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));
320 // Now make both DFDs into Capsicum capabilities.
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));
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.
330 FORK_TEST_F(OpenatTest, InCapabilityMode) {
331 EXPECT_OK(cap_enter()); // Enter capability mode
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));
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);
344 #if !defined(O_RESOLVE_BENEATH) && defined(O_BENEATH)
345 #define O_RESOLVE_BENEATH O_BENEATH
348 #ifdef O_RESOLVE_BENEATH
349 TEST_F(OpenatTest, WithFlag) {
350 CheckPolicing(O_RESOLVE_BENEATH);
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));
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);
362 FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) {
363 EXPECT_OK(cap_enter()); // Enter capability mode
364 CheckPolicing(O_RESOLVE_BENEATH);