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