Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/capsicum-test/openat.cc
39475 views
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
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.
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
#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"
153
class 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
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));
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
330
FORK_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
349
TEST_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
362
FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) {
363
EXPECT_OK(cap_enter()); // Enter capability mode
364
CheckPolicing(O_RESOLVE_BENEATH);
365
}
366
#endif
367
368