]>
Commit | Line | Data |
---|---|---|
e1199815 MS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Landlock tests - Filesystem | |
4 | * | |
5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | |
6 | * Copyright © 2020 ANSSI | |
55e55920 | 7 | * Copyright © 2020-2022 Microsoft Corporation |
e1199815 MS |
8 | */ |
9 | ||
10 | #define _GNU_SOURCE | |
11 | #include <fcntl.h> | |
12 | #include <linux/landlock.h> | |
35ca4239 | 13 | #include <linux/magic.h> |
e1199815 | 14 | #include <sched.h> |
366617a6 | 15 | #include <stdio.h> |
e1199815 MS |
16 | #include <string.h> |
17 | #include <sys/capability.h> | |
18 | #include <sys/mount.h> | |
19 | #include <sys/prctl.h> | |
20 | #include <sys/sendfile.h> | |
21 | #include <sys/stat.h> | |
22 | #include <sys/sysmacros.h> | |
35ca4239 | 23 | #include <sys/vfs.h> |
e1199815 MS |
24 | #include <unistd.h> |
25 | ||
26 | #include "common.h" | |
27 | ||
87129ef1 MS |
28 | #ifndef renameat2 |
29 | int renameat2(int olddirfd, const char *oldpath, int newdirfd, | |
30 | const char *newpath, unsigned int flags) | |
31 | { | |
32 | return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, | |
33 | flags); | |
34 | } | |
35 | #endif | |
36 | ||
37 | #ifndef RENAME_EXCHANGE | |
38 | #define RENAME_EXCHANGE (1 << 1) | |
39 | #endif | |
40 | ||
371183fa MS |
41 | #define TMP_DIR "tmp" |
42 | #define BINARY_PATH "./true" | |
e1199815 MS |
43 | |
44 | /* Paths (sibling number and depth) */ | |
45 | static const char dir_s1d1[] = TMP_DIR "/s1d1"; | |
46 | static const char file1_s1d1[] = TMP_DIR "/s1d1/f1"; | |
47 | static const char file2_s1d1[] = TMP_DIR "/s1d1/f2"; | |
48 | static const char dir_s1d2[] = TMP_DIR "/s1d1/s1d2"; | |
49 | static const char file1_s1d2[] = TMP_DIR "/s1d1/s1d2/f1"; | |
50 | static const char file2_s1d2[] = TMP_DIR "/s1d1/s1d2/f2"; | |
51 | static const char dir_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3"; | |
52 | static const char file1_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f1"; | |
53 | static const char file2_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f2"; | |
54 | ||
55 | static const char dir_s2d1[] = TMP_DIR "/s2d1"; | |
56 | static const char file1_s2d1[] = TMP_DIR "/s2d1/f1"; | |
57 | static const char dir_s2d2[] = TMP_DIR "/s2d1/s2d2"; | |
58 | static const char file1_s2d2[] = TMP_DIR "/s2d1/s2d2/f1"; | |
59 | static const char dir_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3"; | |
60 | static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; | |
61 | static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; | |
62 | ||
63 | static const char dir_s3d1[] = TMP_DIR "/s3d1"; | |
225351ab | 64 | static const char file1_s3d1[] = TMP_DIR "/s3d1/f1"; |
e1199815 MS |
65 | /* dir_s3d2 is a mount point. */ |
66 | static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; | |
67 | static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; | |
68 | ||
69 | /* | |
70 | * layout1 hierarchy: | |
71 | * | |
72 | * tmp | |
73 | * ├── s1d1 | |
74 | * │ ├── f1 | |
75 | * │ ├── f2 | |
76 | * │ └── s1d2 | |
77 | * │ ├── f1 | |
78 | * │ ├── f2 | |
79 | * │ └── s1d3 | |
80 | * │ ├── f1 | |
81 | * │ └── f2 | |
82 | * ├── s2d1 | |
83 | * │ ├── f1 | |
84 | * │ └── s2d2 | |
85 | * │ ├── f1 | |
86 | * │ └── s2d3 | |
87 | * │ ├── f1 | |
88 | * │ └── f2 | |
89 | * └── s3d1 | |
225351ab | 90 | * ├── f1 |
e1199815 MS |
91 | * └── s3d2 |
92 | * └── s3d3 | |
93 | */ | |
94 | ||
366617a6 JX |
95 | static bool fgrep(FILE *const inf, const char *const str) |
96 | { | |
97 | char line[32]; | |
98 | const int slen = strlen(str); | |
99 | ||
100 | while (!feof(inf)) { | |
101 | if (!fgets(line, sizeof(line), inf)) | |
102 | break; | |
103 | if (strncmp(line, str, slen)) | |
104 | continue; | |
105 | ||
106 | return true; | |
107 | } | |
108 | ||
109 | return false; | |
110 | } | |
111 | ||
3de64b65 | 112 | static bool supports_filesystem(const char *const filesystem) |
366617a6 | 113 | { |
3de64b65 MS |
114 | char str[32]; |
115 | int len; | |
2a201549 | 116 | bool res = true; |
366617a6 JX |
117 | FILE *const inf = fopen("/proc/filesystems", "r"); |
118 | ||
119 | /* | |
120 | * Consider that the filesystem is supported if we cannot get the | |
121 | * supported ones. | |
122 | */ | |
123 | if (!inf) | |
124 | return true; | |
125 | ||
04f9070e MS |
126 | /* filesystem can be null for bind mounts. */ |
127 | if (!filesystem) | |
2a201549 | 128 | goto out; |
04f9070e | 129 | |
3de64b65 MS |
130 | len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem); |
131 | if (len >= sizeof(str)) | |
132 | /* Ignores too-long filesystem names. */ | |
2a201549 | 133 | goto out; |
3de64b65 MS |
134 | |
135 | res = fgrep(inf, str); | |
2a201549 DX |
136 | |
137 | out: | |
366617a6 JX |
138 | fclose(inf); |
139 | return res; | |
140 | } | |
141 | ||
35ca4239 MS |
142 | static bool cwd_matches_fs(unsigned int fs_magic) |
143 | { | |
144 | struct statfs statfs_buf; | |
145 | ||
146 | if (!fs_magic) | |
147 | return true; | |
148 | ||
149 | if (statfs(".", &statfs_buf)) | |
150 | return true; | |
151 | ||
152 | return statfs_buf.f_type == fs_magic; | |
153 | } | |
154 | ||
e1199815 | 155 | static void mkdir_parents(struct __test_metadata *const _metadata, |
371183fa | 156 | const char *const path) |
e1199815 MS |
157 | { |
158 | char *walker; | |
159 | const char *parent; | |
160 | int i, err; | |
161 | ||
162 | ASSERT_NE(path[0], '\0'); | |
163 | walker = strdup(path); | |
164 | ASSERT_NE(NULL, walker); | |
165 | parent = walker; | |
166 | for (i = 1; walker[i]; i++) { | |
167 | if (walker[i] != '/') | |
168 | continue; | |
169 | walker[i] = '\0'; | |
170 | err = mkdir(parent, 0700); | |
371183fa MS |
171 | ASSERT_FALSE(err && errno != EEXIST) |
172 | { | |
173 | TH_LOG("Failed to create directory \"%s\": %s", parent, | |
174 | strerror(errno)); | |
e1199815 MS |
175 | } |
176 | walker[i] = '/'; | |
177 | } | |
178 | free(walker); | |
179 | } | |
180 | ||
181 | static void create_directory(struct __test_metadata *const _metadata, | |
371183fa | 182 | const char *const path) |
e1199815 MS |
183 | { |
184 | mkdir_parents(_metadata, path); | |
371183fa MS |
185 | ASSERT_EQ(0, mkdir(path, 0700)) |
186 | { | |
e1199815 | 187 | TH_LOG("Failed to create directory \"%s\": %s", path, |
371183fa | 188 | strerror(errno)); |
e1199815 MS |
189 | } |
190 | } | |
191 | ||
192 | static void create_file(struct __test_metadata *const _metadata, | |
371183fa | 193 | const char *const path) |
e1199815 MS |
194 | { |
195 | mkdir_parents(_metadata, path); | |
371183fa MS |
196 | ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0)) |
197 | { | |
e1199815 | 198 | TH_LOG("Failed to create file \"%s\": %s", path, |
371183fa | 199 | strerror(errno)); |
e1199815 MS |
200 | } |
201 | } | |
202 | ||
203 | static int remove_path(const char *const path) | |
204 | { | |
205 | char *walker; | |
206 | int i, ret, err = 0; | |
207 | ||
208 | walker = strdup(path); | |
209 | if (!walker) { | |
210 | err = ENOMEM; | |
211 | goto out; | |
212 | } | |
213 | if (unlink(path) && rmdir(path)) { | |
f4056b92 | 214 | if (errno != ENOENT && errno != ENOTDIR) |
e1199815 MS |
215 | err = errno; |
216 | goto out; | |
217 | } | |
218 | for (i = strlen(walker); i > 0; i--) { | |
219 | if (walker[i] != '/') | |
220 | continue; | |
221 | walker[i] = '\0'; | |
222 | ret = rmdir(walker); | |
223 | if (ret) { | |
224 | if (errno != ENOTEMPTY && errno != EBUSY) | |
225 | err = errno; | |
226 | goto out; | |
227 | } | |
228 | if (strcmp(walker, TMP_DIR) == 0) | |
229 | goto out; | |
230 | } | |
231 | ||
232 | out: | |
233 | free(walker); | |
234 | return err; | |
235 | } | |
236 | ||
55ab3fbe MS |
237 | struct mnt_opt { |
238 | const char *const source; | |
239 | const char *const type; | |
240 | const unsigned long flags; | |
241 | const char *const data; | |
242 | }; | |
243 | ||
40b7835e HY |
244 | #define MNT_TMP_DATA "size=4m,mode=700" |
245 | ||
246 | static const struct mnt_opt mnt_tmp = { | |
55ab3fbe | 247 | .type = "tmpfs", |
40b7835e | 248 | .data = MNT_TMP_DATA, |
55ab3fbe MS |
249 | }; |
250 | ||
251 | static int mount_opt(const struct mnt_opt *const mnt, const char *const target) | |
252 | { | |
253 | return mount(mnt->source ?: mnt->type, target, mnt->type, mnt->flags, | |
254 | mnt->data); | |
255 | } | |
256 | ||
257 | static void prepare_layout_opt(struct __test_metadata *const _metadata, | |
258 | const struct mnt_opt *const mnt) | |
e1199815 MS |
259 | { |
260 | disable_caps(_metadata); | |
261 | umask(0077); | |
262 | create_directory(_metadata, TMP_DIR); | |
263 | ||
264 | /* | |
265 | * Do not pollute the rest of the system: creates a private mount point | |
266 | * for tests relying on pivot_root(2) and move_mount(2). | |
267 | */ | |
268 | set_cap(_metadata, CAP_SYS_ADMIN); | |
04f9070e | 269 | ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWCGROUP)); |
55ab3fbe MS |
270 | ASSERT_EQ(0, mount_opt(mnt, TMP_DIR)) |
271 | { | |
272 | TH_LOG("Failed to mount the %s filesystem: %s", mnt->type, | |
273 | strerror(errno)); | |
274 | /* | |
275 | * FIXTURE_TEARDOWN() is not called when FIXTURE_SETUP() | |
276 | * failed, so we need to explicitly do a minimal cleanup to | |
277 | * avoid cascading errors with other tests that don't depend on | |
278 | * the same filesystem. | |
279 | */ | |
280 | remove_path(TMP_DIR); | |
281 | } | |
e1199815 MS |
282 | ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL)); |
283 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
284 | } | |
285 | ||
55ab3fbe MS |
286 | static void prepare_layout(struct __test_metadata *const _metadata) |
287 | { | |
41cca054 MS |
288 | _metadata->teardown_parent = true; |
289 | ||
55ab3fbe MS |
290 | prepare_layout_opt(_metadata, &mnt_tmp); |
291 | } | |
292 | ||
e1199815 MS |
293 | static void cleanup_layout(struct __test_metadata *const _metadata) |
294 | { | |
295 | set_cap(_metadata, CAP_SYS_ADMIN); | |
7e4042ab MS |
296 | if (umount(TMP_DIR)) { |
297 | /* | |
298 | * According to the test environment, the mount point of the | |
299 | * current directory may be shared or not, which changes the | |
300 | * visibility of the nested TMP_DIR mount point for the test's | |
301 | * parent process doing this cleanup. | |
302 | */ | |
303 | ASSERT_EQ(EINVAL, errno); | |
304 | } | |
e1199815 MS |
305 | clear_cap(_metadata, CAP_SYS_ADMIN); |
306 | EXPECT_EQ(0, remove_path(TMP_DIR)); | |
307 | } | |
308 | ||
592efeb4 MS |
309 | /* clang-format off */ |
310 | FIXTURE(layout0) {}; | |
311 | /* clang-format on */ | |
312 | ||
313 | FIXTURE_SETUP(layout0) | |
314 | { | |
315 | prepare_layout(_metadata); | |
316 | } | |
317 | ||
318 | FIXTURE_TEARDOWN(layout0) | |
319 | { | |
320 | cleanup_layout(_metadata); | |
321 | } | |
322 | ||
e1199815 MS |
323 | static void create_layout1(struct __test_metadata *const _metadata) |
324 | { | |
325 | create_file(_metadata, file1_s1d1); | |
326 | create_file(_metadata, file1_s1d2); | |
327 | create_file(_metadata, file1_s1d3); | |
328 | create_file(_metadata, file2_s1d1); | |
329 | create_file(_metadata, file2_s1d2); | |
330 | create_file(_metadata, file2_s1d3); | |
331 | ||
332 | create_file(_metadata, file1_s2d1); | |
333 | create_file(_metadata, file1_s2d2); | |
334 | create_file(_metadata, file1_s2d3); | |
335 | create_file(_metadata, file2_s2d3); | |
336 | ||
225351ab | 337 | create_file(_metadata, file1_s3d1); |
e1199815 MS |
338 | create_directory(_metadata, dir_s3d2); |
339 | set_cap(_metadata, CAP_SYS_ADMIN); | |
55ab3fbe | 340 | ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2)); |
e1199815 MS |
341 | clear_cap(_metadata, CAP_SYS_ADMIN); |
342 | ||
343 | ASSERT_EQ(0, mkdir(dir_s3d3, 0700)); | |
344 | } | |
345 | ||
346 | static void remove_layout1(struct __test_metadata *const _metadata) | |
347 | { | |
348 | EXPECT_EQ(0, remove_path(file2_s1d3)); | |
349 | EXPECT_EQ(0, remove_path(file2_s1d2)); | |
350 | EXPECT_EQ(0, remove_path(file2_s1d1)); | |
351 | EXPECT_EQ(0, remove_path(file1_s1d3)); | |
352 | EXPECT_EQ(0, remove_path(file1_s1d2)); | |
353 | EXPECT_EQ(0, remove_path(file1_s1d1)); | |
04f9070e | 354 | EXPECT_EQ(0, remove_path(dir_s1d3)); |
e1199815 MS |
355 | |
356 | EXPECT_EQ(0, remove_path(file2_s2d3)); | |
357 | EXPECT_EQ(0, remove_path(file1_s2d3)); | |
358 | EXPECT_EQ(0, remove_path(file1_s2d2)); | |
359 | EXPECT_EQ(0, remove_path(file1_s2d1)); | |
04f9070e | 360 | EXPECT_EQ(0, remove_path(dir_s2d2)); |
e1199815 | 361 | |
225351ab | 362 | EXPECT_EQ(0, remove_path(file1_s3d1)); |
e1199815 MS |
363 | EXPECT_EQ(0, remove_path(dir_s3d3)); |
364 | set_cap(_metadata, CAP_SYS_ADMIN); | |
365 | umount(dir_s3d2); | |
366 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
367 | EXPECT_EQ(0, remove_path(dir_s3d2)); | |
368 | } | |
369 | ||
4598d9ab MS |
370 | /* clang-format off */ |
371 | FIXTURE(layout1) {}; | |
372 | /* clang-format on */ | |
e1199815 MS |
373 | |
374 | FIXTURE_SETUP(layout1) | |
375 | { | |
376 | prepare_layout(_metadata); | |
377 | ||
378 | create_layout1(_metadata); | |
379 | } | |
380 | ||
381 | FIXTURE_TEARDOWN(layout1) | |
382 | { | |
383 | remove_layout1(_metadata); | |
384 | ||
385 | cleanup_layout(_metadata); | |
386 | } | |
387 | ||
388 | /* | |
389 | * This helper enables to use the ASSERT_* macros and print the line number | |
390 | * pointing to the test caller. | |
391 | */ | |
371183fa MS |
392 | static int test_open_rel(const int dirfd, const char *const path, |
393 | const int flags) | |
e1199815 MS |
394 | { |
395 | int fd; | |
396 | ||
397 | /* Works with file and directories. */ | |
398 | fd = openat(dirfd, path, flags | O_CLOEXEC); | |
399 | if (fd < 0) | |
400 | return errno; | |
401 | /* | |
402 | * Mixing error codes from close(2) and open(2) should not lead to any | |
403 | * (access type) confusion for this test. | |
404 | */ | |
405 | if (close(fd) != 0) | |
406 | return errno; | |
407 | return 0; | |
408 | } | |
409 | ||
410 | static int test_open(const char *const path, const int flags) | |
411 | { | |
412 | return test_open_rel(AT_FDCWD, path, flags); | |
413 | } | |
414 | ||
415 | TEST_F_FORK(layout1, no_restriction) | |
416 | { | |
417 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
418 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | |
419 | ASSERT_EQ(0, test_open(file2_s1d1, O_RDONLY)); | |
420 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | |
421 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
422 | ASSERT_EQ(0, test_open(file2_s1d2, O_RDONLY)); | |
423 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | |
424 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
425 | ||
426 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); | |
427 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); | |
428 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); | |
429 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); | |
430 | ASSERT_EQ(0, test_open(dir_s2d3, O_RDONLY)); | |
431 | ASSERT_EQ(0, test_open(file1_s2d3, O_RDONLY)); | |
432 | ||
433 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | |
434 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | |
435 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | |
436 | } | |
437 | ||
438 | TEST_F_FORK(layout1, inval) | |
439 | { | |
440 | struct landlock_path_beneath_attr path_beneath = { | |
441 | .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 442 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
443 | .parent_fd = -1, |
444 | }; | |
445 | struct landlock_ruleset_attr ruleset_attr = { | |
446 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 447 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
448 | }; |
449 | int ruleset_fd; | |
450 | ||
371183fa MS |
451 | path_beneath.parent_fd = |
452 | open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); | |
e1199815 MS |
453 | ASSERT_LE(0, path_beneath.parent_fd); |
454 | ||
455 | ruleset_fd = open(dir_s1d1, O_PATH | O_DIRECTORY | O_CLOEXEC); | |
456 | ASSERT_LE(0, ruleset_fd); | |
457 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 458 | &path_beneath, 0)); |
e1199815 MS |
459 | /* Returns EBADF because ruleset_fd is not a landlock-ruleset FD. */ |
460 | ASSERT_EQ(EBADF, errno); | |
461 | ASSERT_EQ(0, close(ruleset_fd)); | |
462 | ||
463 | ruleset_fd = open(dir_s1d1, O_DIRECTORY | O_CLOEXEC); | |
464 | ASSERT_LE(0, ruleset_fd); | |
465 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 466 | &path_beneath, 0)); |
e1199815 MS |
467 | /* Returns EBADFD because ruleset_fd is not a valid ruleset. */ |
468 | ASSERT_EQ(EBADFD, errno); | |
469 | ASSERT_EQ(0, close(ruleset_fd)); | |
470 | ||
471 | /* Gets a real ruleset. */ | |
371183fa MS |
472 | ruleset_fd = |
473 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
e1199815 MS |
474 | ASSERT_LE(0, ruleset_fd); |
475 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 476 | &path_beneath, 0)); |
e1199815 MS |
477 | ASSERT_EQ(0, close(path_beneath.parent_fd)); |
478 | ||
479 | /* Tests without O_PATH. */ | |
480 | path_beneath.parent_fd = open(dir_s1d2, O_DIRECTORY | O_CLOEXEC); | |
481 | ASSERT_LE(0, path_beneath.parent_fd); | |
482 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 483 | &path_beneath, 0)); |
e1199815 MS |
484 | ASSERT_EQ(0, close(path_beneath.parent_fd)); |
485 | ||
486 | /* Tests with a ruleset FD. */ | |
487 | path_beneath.parent_fd = ruleset_fd; | |
488 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 489 | &path_beneath, 0)); |
e1199815 MS |
490 | ASSERT_EQ(EBADFD, errno); |
491 | ||
492 | /* Checks unhandled allowed_access. */ | |
371183fa MS |
493 | path_beneath.parent_fd = |
494 | open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); | |
e1199815 MS |
495 | ASSERT_LE(0, path_beneath.parent_fd); |
496 | ||
497 | /* Test with legitimate values. */ | |
498 | path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE; | |
499 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 500 | &path_beneath, 0)); |
e1199815 MS |
501 | ASSERT_EQ(EINVAL, errno); |
502 | path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE; | |
503 | ||
55e55920 MS |
504 | /* Tests with denied-by-default access right. */ |
505 | path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER; | |
506 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
507 | &path_beneath, 0)); | |
508 | ASSERT_EQ(EINVAL, errno); | |
509 | path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_REFER; | |
510 | ||
e1199815 MS |
511 | /* Test with unknown (64-bits) value. */ |
512 | path_beneath.allowed_access |= (1ULL << 60); | |
513 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 514 | &path_beneath, 0)); |
e1199815 MS |
515 | ASSERT_EQ(EINVAL, errno); |
516 | path_beneath.allowed_access &= ~(1ULL << 60); | |
517 | ||
518 | /* Test with no access. */ | |
519 | path_beneath.allowed_access = 0; | |
520 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 521 | &path_beneath, 0)); |
e1199815 MS |
522 | ASSERT_EQ(ENOMSG, errno); |
523 | path_beneath.allowed_access &= ~(1ULL << 60); | |
524 | ||
525 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | |
526 | ||
527 | /* Enforces the ruleset. */ | |
528 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | |
529 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); | |
530 | ||
531 | ASSERT_EQ(0, close(ruleset_fd)); | |
532 | } | |
533 | ||
4598d9ab MS |
534 | /* clang-format off */ |
535 | ||
e1199815 MS |
536 | #define ACCESS_FILE ( \ |
537 | LANDLOCK_ACCESS_FS_EXECUTE | \ | |
538 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | |
b9f5ce27 GN |
539 | LANDLOCK_ACCESS_FS_READ_FILE | \ |
540 | LANDLOCK_ACCESS_FS_TRUNCATE) | |
e1199815 | 541 | |
b9f5ce27 | 542 | #define ACCESS_LAST LANDLOCK_ACCESS_FS_TRUNCATE |
e1199815 MS |
543 | |
544 | #define ACCESS_ALL ( \ | |
545 | ACCESS_FILE | \ | |
546 | LANDLOCK_ACCESS_FS_READ_DIR | \ | |
547 | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ | |
548 | LANDLOCK_ACCESS_FS_REMOVE_FILE | \ | |
549 | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ | |
550 | LANDLOCK_ACCESS_FS_MAKE_DIR | \ | |
551 | LANDLOCK_ACCESS_FS_MAKE_REG | \ | |
552 | LANDLOCK_ACCESS_FS_MAKE_SOCK | \ | |
553 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ | |
554 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ | |
b91c3e4e | 555 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ |
b9f5ce27 | 556 | LANDLOCK_ACCESS_FS_REFER) |
e1199815 | 557 | |
4598d9ab MS |
558 | /* clang-format on */ |
559 | ||
d18955d0 | 560 | TEST_F_FORK(layout1, file_and_dir_access_rights) |
e1199815 MS |
561 | { |
562 | __u64 access; | |
563 | int err; | |
d18955d0 MS |
564 | struct landlock_path_beneath_attr path_beneath_file = {}, |
565 | path_beneath_dir = {}; | |
e1199815 MS |
566 | struct landlock_ruleset_attr ruleset_attr = { |
567 | .handled_access_fs = ACCESS_ALL, | |
568 | }; | |
371183fa MS |
569 | const int ruleset_fd = |
570 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
e1199815 MS |
571 | |
572 | ASSERT_LE(0, ruleset_fd); | |
573 | ||
574 | /* Tests access rights for files. */ | |
d18955d0 MS |
575 | path_beneath_file.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); |
576 | ASSERT_LE(0, path_beneath_file.parent_fd); | |
577 | ||
578 | /* Tests access rights for directories. */ | |
579 | path_beneath_dir.parent_fd = | |
580 | open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC); | |
581 | ASSERT_LE(0, path_beneath_dir.parent_fd); | |
582 | ||
e1199815 | 583 | for (access = 1; access <= ACCESS_LAST; access <<= 1) { |
d18955d0 MS |
584 | path_beneath_dir.allowed_access = access; |
585 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, | |
586 | LANDLOCK_RULE_PATH_BENEATH, | |
587 | &path_beneath_dir, 0)); | |
588 | ||
589 | path_beneath_file.allowed_access = access; | |
e1199815 | 590 | err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
d18955d0 MS |
591 | &path_beneath_file, 0); |
592 | if (access & ACCESS_FILE) { | |
e1199815 MS |
593 | ASSERT_EQ(0, err); |
594 | } else { | |
595 | ASSERT_EQ(-1, err); | |
596 | ASSERT_EQ(EINVAL, errno); | |
597 | } | |
598 | } | |
d18955d0 MS |
599 | ASSERT_EQ(0, close(path_beneath_file.parent_fd)); |
600 | ASSERT_EQ(0, close(path_beneath_dir.parent_fd)); | |
601 | ASSERT_EQ(0, close(ruleset_fd)); | |
e1199815 MS |
602 | } |
603 | ||
6471c9c4 | 604 | TEST_F_FORK(layout0, ruleset_with_unknown_access) |
c56b3bf5 MS |
605 | { |
606 | __u64 access_mask; | |
607 | ||
608 | for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST; | |
609 | access_mask >>= 1) { | |
610 | struct landlock_ruleset_attr ruleset_attr = { | |
611 | .handled_access_fs = access_mask, | |
612 | }; | |
613 | ||
614 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, | |
615 | sizeof(ruleset_attr), 0)); | |
616 | ASSERT_EQ(EINVAL, errno); | |
617 | } | |
618 | } | |
619 | ||
6471c9c4 MS |
620 | TEST_F_FORK(layout0, rule_with_unknown_access) |
621 | { | |
622 | __u64 access; | |
623 | struct landlock_path_beneath_attr path_beneath = {}; | |
624 | const struct landlock_ruleset_attr ruleset_attr = { | |
625 | .handled_access_fs = ACCESS_ALL, | |
626 | }; | |
627 | const int ruleset_fd = | |
628 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
629 | ||
630 | ASSERT_LE(0, ruleset_fd); | |
631 | ||
632 | path_beneath.parent_fd = | |
633 | open(TMP_DIR, O_PATH | O_DIRECTORY | O_CLOEXEC); | |
634 | ASSERT_LE(0, path_beneath.parent_fd); | |
635 | ||
636 | for (access = 1ULL << 63; access != ACCESS_LAST; access >>= 1) { | |
637 | path_beneath.allowed_access = access; | |
638 | EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, | |
639 | LANDLOCK_RULE_PATH_BENEATH, | |
640 | &path_beneath, 0)); | |
641 | EXPECT_EQ(EINVAL, errno); | |
642 | } | |
643 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | |
644 | ASSERT_EQ(0, close(ruleset_fd)); | |
645 | } | |
646 | ||
e2780a0b MS |
647 | TEST_F_FORK(layout1, rule_with_unhandled_access) |
648 | { | |
649 | struct landlock_ruleset_attr ruleset_attr = { | |
650 | .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, | |
651 | }; | |
652 | struct landlock_path_beneath_attr path_beneath = {}; | |
653 | int ruleset_fd; | |
654 | __u64 access; | |
655 | ||
656 | ruleset_fd = | |
657 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
658 | ASSERT_LE(0, ruleset_fd); | |
659 | ||
660 | path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); | |
661 | ASSERT_LE(0, path_beneath.parent_fd); | |
662 | ||
663 | for (access = 1; access > 0; access <<= 1) { | |
664 | int err; | |
665 | ||
666 | path_beneath.allowed_access = access; | |
667 | err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
668 | &path_beneath, 0); | |
669 | if (access == ruleset_attr.handled_access_fs) { | |
670 | EXPECT_EQ(0, err); | |
671 | } else { | |
672 | EXPECT_EQ(-1, err); | |
673 | EXPECT_EQ(EINVAL, errno); | |
674 | } | |
675 | } | |
676 | ||
677 | EXPECT_EQ(0, close(path_beneath.parent_fd)); | |
678 | EXPECT_EQ(0, close(ruleset_fd)); | |
679 | } | |
680 | ||
e1199815 | 681 | static void add_path_beneath(struct __test_metadata *const _metadata, |
371183fa MS |
682 | const int ruleset_fd, const __u64 allowed_access, |
683 | const char *const path) | |
e1199815 MS |
684 | { |
685 | struct landlock_path_beneath_attr path_beneath = { | |
686 | .allowed_access = allowed_access, | |
687 | }; | |
688 | ||
689 | path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC); | |
371183fa MS |
690 | ASSERT_LE(0, path_beneath.parent_fd) |
691 | { | |
e1199815 | 692 | TH_LOG("Failed to open directory \"%s\": %s", path, |
371183fa | 693 | strerror(errno)); |
e1199815 MS |
694 | } |
695 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa MS |
696 | &path_beneath, 0)) |
697 | { | |
e1199815 | 698 | TH_LOG("Failed to update the ruleset with \"%s\": %s", path, |
371183fa | 699 | strerror(errno)); |
e1199815 MS |
700 | } |
701 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | |
702 | } | |
703 | ||
704 | struct rule { | |
705 | const char *path; | |
706 | __u64 access; | |
707 | }; | |
708 | ||
4598d9ab MS |
709 | /* clang-format off */ |
710 | ||
e1199815 MS |
711 | #define ACCESS_RO ( \ |
712 | LANDLOCK_ACCESS_FS_READ_FILE | \ | |
713 | LANDLOCK_ACCESS_FS_READ_DIR) | |
714 | ||
715 | #define ACCESS_RW ( \ | |
716 | ACCESS_RO | \ | |
717 | LANDLOCK_ACCESS_FS_WRITE_FILE) | |
718 | ||
4598d9ab MS |
719 | /* clang-format on */ |
720 | ||
e1199815 | 721 | static int create_ruleset(struct __test_metadata *const _metadata, |
371183fa MS |
722 | const __u64 handled_access_fs, |
723 | const struct rule rules[]) | |
e1199815 MS |
724 | { |
725 | int ruleset_fd, i; | |
726 | struct landlock_ruleset_attr ruleset_attr = { | |
727 | .handled_access_fs = handled_access_fs, | |
728 | }; | |
729 | ||
371183fa MS |
730 | ASSERT_NE(NULL, rules) |
731 | { | |
e1199815 MS |
732 | TH_LOG("No rule list"); |
733 | } | |
371183fa MS |
734 | ASSERT_NE(NULL, rules[0].path) |
735 | { | |
e1199815 MS |
736 | TH_LOG("Empty rule list"); |
737 | } | |
738 | ||
371183fa MS |
739 | ruleset_fd = |
740 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
741 | ASSERT_LE(0, ruleset_fd) | |
742 | { | |
e1199815 MS |
743 | TH_LOG("Failed to create a ruleset: %s", strerror(errno)); |
744 | } | |
745 | ||
746 | for (i = 0; rules[i].path; i++) { | |
747 | add_path_beneath(_metadata, ruleset_fd, rules[i].access, | |
371183fa | 748 | rules[i].path); |
e1199815 MS |
749 | } |
750 | return ruleset_fd; | |
751 | } | |
752 | ||
592efeb4 | 753 | TEST_F_FORK(layout0, proc_nsfs) |
e1199815 MS |
754 | { |
755 | const struct rule rules[] = { | |
756 | { | |
757 | .path = "/dev/null", | |
758 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 759 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 | 760 | }, |
135464f9 | 761 | {}, |
e1199815 MS |
762 | }; |
763 | struct landlock_path_beneath_attr path_beneath; | |
371183fa MS |
764 | const int ruleset_fd = create_ruleset( |
765 | _metadata, rules[0].access | LANDLOCK_ACCESS_FS_READ_DIR, | |
766 | rules); | |
e1199815 MS |
767 | |
768 | ASSERT_LE(0, ruleset_fd); | |
769 | ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); | |
770 | ||
771 | enforce_ruleset(_metadata, ruleset_fd); | |
772 | ||
773 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | |
774 | ASSERT_EQ(EACCES, test_open("/dev", O_RDONLY)); | |
775 | ASSERT_EQ(0, test_open("/dev/null", O_RDONLY)); | |
776 | ASSERT_EQ(EACCES, test_open("/dev/full", O_RDONLY)); | |
777 | ||
778 | ASSERT_EQ(EACCES, test_open("/proc", O_RDONLY)); | |
779 | ASSERT_EQ(EACCES, test_open("/proc/self", O_RDONLY)); | |
780 | ASSERT_EQ(EACCES, test_open("/proc/self/ns", O_RDONLY)); | |
781 | /* | |
782 | * Because nsfs is an internal filesystem, /proc/self/ns/mnt is a | |
783 | * disconnected path. Such path cannot be identified and must then be | |
784 | * allowed. | |
785 | */ | |
786 | ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); | |
787 | ||
788 | /* | |
789 | * Checks that it is not possible to add nsfs-like filesystem | |
790 | * references to a ruleset. | |
791 | */ | |
792 | path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 793 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
794 | path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC); |
795 | ASSERT_LE(0, path_beneath.parent_fd); | |
796 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
371183fa | 797 | &path_beneath, 0)); |
e1199815 MS |
798 | ASSERT_EQ(EBADFD, errno); |
799 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | |
800 | } | |
801 | ||
592efeb4 | 802 | TEST_F_FORK(layout0, unpriv) |
371183fa | 803 | { |
e1199815 MS |
804 | const struct rule rules[] = { |
805 | { | |
592efeb4 | 806 | .path = TMP_DIR, |
e1199815 MS |
807 | .access = ACCESS_RO, |
808 | }, | |
135464f9 | 809 | {}, |
e1199815 MS |
810 | }; |
811 | int ruleset_fd; | |
812 | ||
813 | drop_caps(_metadata); | |
814 | ||
815 | ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); | |
816 | ASSERT_LE(0, ruleset_fd); | |
817 | ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); | |
818 | ASSERT_EQ(EPERM, errno); | |
819 | ||
820 | /* enforce_ruleset() calls prctl(no_new_privs). */ | |
821 | enforce_ruleset(_metadata, ruleset_fd); | |
822 | ASSERT_EQ(0, close(ruleset_fd)); | |
823 | } | |
824 | ||
825 | TEST_F_FORK(layout1, effective_access) | |
826 | { | |
827 | const struct rule rules[] = { | |
828 | { | |
829 | .path = dir_s1d2, | |
830 | .access = ACCESS_RO, | |
831 | }, | |
832 | { | |
833 | .path = file1_s2d2, | |
834 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 835 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 | 836 | }, |
135464f9 | 837 | {}, |
e1199815 MS |
838 | }; |
839 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
840 | char buf; | |
841 | int reg_fd; | |
842 | ||
843 | ASSERT_LE(0, ruleset_fd); | |
844 | enforce_ruleset(_metadata, ruleset_fd); | |
845 | ASSERT_EQ(0, close(ruleset_fd)); | |
846 | ||
d1788ad9 | 847 | /* Tests on a directory (with or without O_PATH). */ |
e1199815 | 848 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); |
d1788ad9 | 849 | ASSERT_EQ(0, test_open("/", O_RDONLY | O_PATH)); |
e1199815 | 850 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); |
d1788ad9 | 851 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_PATH)); |
e1199815 | 852 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); |
d1788ad9 MS |
853 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY | O_PATH)); |
854 | ||
e1199815 MS |
855 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); |
856 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
857 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | |
858 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
859 | ||
d1788ad9 | 860 | /* Tests on a file (with or without O_PATH). */ |
e1199815 | 861 | ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY)); |
d1788ad9 MS |
862 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_PATH)); |
863 | ||
e1199815 MS |
864 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); |
865 | ||
866 | /* Checks effective read and write actions. */ | |
867 | reg_fd = open(file1_s2d2, O_RDWR | O_CLOEXEC); | |
868 | ASSERT_LE(0, reg_fd); | |
869 | ASSERT_EQ(1, write(reg_fd, ".", 1)); | |
870 | ASSERT_LE(0, lseek(reg_fd, 0, SEEK_SET)); | |
871 | ASSERT_EQ(1, read(reg_fd, &buf, 1)); | |
872 | ASSERT_EQ('.', buf); | |
873 | ASSERT_EQ(0, close(reg_fd)); | |
874 | ||
875 | /* Just in case, double-checks effective actions. */ | |
876 | reg_fd = open(file1_s2d2, O_RDONLY | O_CLOEXEC); | |
877 | ASSERT_LE(0, reg_fd); | |
878 | ASSERT_EQ(-1, write(reg_fd, &buf, 1)); | |
879 | ASSERT_EQ(EBADF, errno); | |
880 | ASSERT_EQ(0, close(reg_fd)); | |
881 | } | |
882 | ||
883 | TEST_F_FORK(layout1, unhandled_access) | |
884 | { | |
885 | const struct rule rules[] = { | |
886 | { | |
887 | .path = dir_s1d2, | |
888 | .access = ACCESS_RO, | |
889 | }, | |
135464f9 | 890 | {}, |
e1199815 MS |
891 | }; |
892 | /* Here, we only handle read accesses, not write accesses. */ | |
893 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); | |
894 | ||
895 | ASSERT_LE(0, ruleset_fd); | |
896 | enforce_ruleset(_metadata, ruleset_fd); | |
897 | ASSERT_EQ(0, close(ruleset_fd)); | |
898 | ||
899 | /* | |
900 | * Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE, | |
901 | * opening for write-only should be allowed, but not read-write. | |
902 | */ | |
903 | ASSERT_EQ(0, test_open(file1_s1d1, O_WRONLY)); | |
904 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
905 | ||
906 | ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); | |
907 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | |
908 | } | |
909 | ||
910 | TEST_F_FORK(layout1, ruleset_overlap) | |
911 | { | |
912 | const struct rule rules[] = { | |
913 | /* These rules should be ORed among them. */ | |
914 | { | |
915 | .path = dir_s1d2, | |
916 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 917 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
918 | }, |
919 | { | |
920 | .path = dir_s1d2, | |
921 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 922 | LANDLOCK_ACCESS_FS_READ_DIR, |
e1199815 | 923 | }, |
135464f9 | 924 | {}, |
e1199815 MS |
925 | }; |
926 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
927 | ||
928 | ASSERT_LE(0, ruleset_fd); | |
929 | enforce_ruleset(_metadata, ruleset_fd); | |
930 | ASSERT_EQ(0, close(ruleset_fd)); | |
931 | ||
932 | /* Checks s1d1 hierarchy. */ | |
933 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
934 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
935 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
936 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
937 | ||
938 | /* Checks s1d2 hierarchy. */ | |
939 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
940 | ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); | |
941 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | |
942 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
943 | ||
944 | /* Checks s1d3 hierarchy. */ | |
945 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
946 | ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); | |
947 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
948 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
949 | } | |
950 | ||
8ba0005f MS |
951 | TEST_F_FORK(layout1, layer_rule_unions) |
952 | { | |
953 | const struct rule layer1[] = { | |
954 | { | |
955 | .path = dir_s1d2, | |
956 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
957 | }, | |
958 | /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ | |
959 | { | |
960 | .path = dir_s1d3, | |
961 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
962 | }, | |
963 | {}, | |
964 | }; | |
965 | const struct rule layer2[] = { | |
966 | /* Doesn't change anything from layer1. */ | |
967 | { | |
968 | .path = dir_s1d2, | |
969 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
970 | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
971 | }, | |
972 | {}, | |
973 | }; | |
974 | const struct rule layer3[] = { | |
975 | /* Only allows write (but not read) to dir_s1d3. */ | |
976 | { | |
977 | .path = dir_s1d2, | |
978 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
979 | }, | |
980 | {}, | |
981 | }; | |
982 | int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1); | |
983 | ||
984 | ASSERT_LE(0, ruleset_fd); | |
985 | enforce_ruleset(_metadata, ruleset_fd); | |
986 | ASSERT_EQ(0, close(ruleset_fd)); | |
987 | ||
988 | /* Checks s1d1 hierarchy with layer1. */ | |
989 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
990 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
991 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
992 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
993 | ||
994 | /* Checks s1d2 hierarchy with layer1. */ | |
995 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
996 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
997 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); | |
998 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
999 | ||
1000 | /* Checks s1d3 hierarchy with layer1. */ | |
1001 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1002 | ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); | |
1003 | /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ | |
1004 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
1005 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1006 | ||
1007 | /* Doesn't change anything from layer1. */ | |
1008 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2); | |
1009 | ASSERT_LE(0, ruleset_fd); | |
1010 | enforce_ruleset(_metadata, ruleset_fd); | |
1011 | ASSERT_EQ(0, close(ruleset_fd)); | |
1012 | ||
1013 | /* Checks s1d1 hierarchy with layer2. */ | |
1014 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
1015 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1016 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
1017 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1018 | ||
1019 | /* Checks s1d2 hierarchy with layer2. */ | |
1020 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
1021 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1022 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); | |
1023 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1024 | ||
1025 | /* Checks s1d3 hierarchy with layer2. */ | |
1026 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1027 | ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); | |
1028 | /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */ | |
1029 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
1030 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1031 | ||
1032 | /* Only allows write (but not read) to dir_s1d3. */ | |
1033 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3); | |
1034 | ASSERT_LE(0, ruleset_fd); | |
1035 | enforce_ruleset(_metadata, ruleset_fd); | |
1036 | ASSERT_EQ(0, close(ruleset_fd)); | |
1037 | ||
1038 | /* Checks s1d1 hierarchy with layer3. */ | |
1039 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
1040 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1041 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
1042 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1043 | ||
1044 | /* Checks s1d2 hierarchy with layer3. */ | |
1045 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); | |
1046 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1047 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); | |
1048 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1049 | ||
1050 | /* Checks s1d3 hierarchy with layer3. */ | |
1051 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); | |
1052 | ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); | |
1053 | /* dir_s1d3 should now deny READ_FILE and WRITE_FILE (O_RDWR). */ | |
1054 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDWR)); | |
1055 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1056 | } | |
1057 | ||
e1199815 MS |
1058 | TEST_F_FORK(layout1, non_overlapping_accesses) |
1059 | { | |
1060 | const struct rule layer1[] = { | |
1061 | { | |
1062 | .path = dir_s1d2, | |
1063 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
1064 | }, | |
135464f9 | 1065 | {}, |
e1199815 MS |
1066 | }; |
1067 | const struct rule layer2[] = { | |
1068 | { | |
1069 | .path = dir_s1d3, | |
1070 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
1071 | }, | |
135464f9 | 1072 | {}, |
e1199815 MS |
1073 | }; |
1074 | int ruleset_fd; | |
1075 | ||
1076 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
1077 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
1078 | ||
371183fa MS |
1079 | ruleset_fd = |
1080 | create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1); | |
e1199815 MS |
1081 | ASSERT_LE(0, ruleset_fd); |
1082 | enforce_ruleset(_metadata, ruleset_fd); | |
1083 | ASSERT_EQ(0, close(ruleset_fd)); | |
1084 | ||
1085 | ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); | |
1086 | ASSERT_EQ(EACCES, errno); | |
1087 | ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); | |
1088 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
1089 | ||
1090 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
371183fa | 1091 | layer2); |
e1199815 MS |
1092 | ASSERT_LE(0, ruleset_fd); |
1093 | enforce_ruleset(_metadata, ruleset_fd); | |
1094 | ASSERT_EQ(0, close(ruleset_fd)); | |
1095 | ||
1096 | /* Unchanged accesses for file creation. */ | |
1097 | ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); | |
1098 | ASSERT_EQ(EACCES, errno); | |
1099 | ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); | |
1100 | ||
1101 | /* Checks file removing. */ | |
1102 | ASSERT_EQ(-1, unlink(file1_s1d2)); | |
1103 | ASSERT_EQ(EACCES, errno); | |
1104 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
1105 | } | |
1106 | ||
1107 | TEST_F_FORK(layout1, interleaved_masked_accesses) | |
1108 | { | |
1109 | /* | |
1110 | * Checks overly restrictive rules: | |
1111 | * layer 1: allows R s1d1/s1d2/s1d3/file1 | |
1112 | * layer 2: allows RW s1d1/s1d2/s1d3 | |
1113 | * allows W s1d1/s1d2 | |
1114 | * denies R s1d1/s1d2 | |
1115 | * layer 3: allows R s1d1 | |
1116 | * layer 4: allows R s1d1/s1d2 | |
1117 | * denies W s1d1/s1d2 | |
1118 | * layer 5: allows R s1d1/s1d2 | |
1119 | * layer 6: allows X ---- | |
1120 | * layer 7: allows W s1d1/s1d2 | |
1121 | * denies R s1d1/s1d2 | |
1122 | */ | |
1123 | const struct rule layer1_read[] = { | |
1124 | /* Allows read access to file1_s1d3 with the first layer. */ | |
1125 | { | |
1126 | .path = file1_s1d3, | |
1127 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
1128 | }, | |
135464f9 | 1129 | {}, |
e1199815 MS |
1130 | }; |
1131 | /* First rule with write restrictions. */ | |
1132 | const struct rule layer2_read_write[] = { | |
1133 | /* Start by granting read-write access via its parent directory... */ | |
1134 | { | |
1135 | .path = dir_s1d3, | |
1136 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 1137 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
1138 | }, |
1139 | /* ...but also denies read access via its grandparent directory. */ | |
1140 | { | |
1141 | .path = dir_s1d2, | |
1142 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
1143 | }, | |
135464f9 | 1144 | {}, |
e1199815 MS |
1145 | }; |
1146 | const struct rule layer3_read[] = { | |
1147 | /* Allows read access via its great-grandparent directory. */ | |
1148 | { | |
1149 | .path = dir_s1d1, | |
1150 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
1151 | }, | |
135464f9 | 1152 | {}, |
e1199815 MS |
1153 | }; |
1154 | const struct rule layer4_read_write[] = { | |
1155 | /* | |
1156 | * Try to confuse the deny access by denying write (but not | |
1157 | * read) access via its grandparent directory. | |
1158 | */ | |
1159 | { | |
1160 | .path = dir_s1d2, | |
1161 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
1162 | }, | |
135464f9 | 1163 | {}, |
e1199815 MS |
1164 | }; |
1165 | const struct rule layer5_read[] = { | |
1166 | /* | |
1167 | * Try to override layer2's deny read access by explicitly | |
1168 | * allowing read access via file1_s1d3's grandparent. | |
1169 | */ | |
1170 | { | |
1171 | .path = dir_s1d2, | |
1172 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
1173 | }, | |
135464f9 | 1174 | {}, |
e1199815 MS |
1175 | }; |
1176 | const struct rule layer6_execute[] = { | |
1177 | /* | |
1178 | * Restricts an unrelated file hierarchy with a new access | |
1179 | * (non-overlapping) type. | |
1180 | */ | |
1181 | { | |
1182 | .path = dir_s2d1, | |
1183 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
1184 | }, | |
135464f9 | 1185 | {}, |
e1199815 MS |
1186 | }; |
1187 | const struct rule layer7_read_write[] = { | |
1188 | /* | |
1189 | * Finally, denies read access to file1_s1d3 via its | |
1190 | * grandparent. | |
1191 | */ | |
1192 | { | |
1193 | .path = dir_s1d2, | |
1194 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
1195 | }, | |
135464f9 | 1196 | {}, |
e1199815 MS |
1197 | }; |
1198 | int ruleset_fd; | |
1199 | ||
1200 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | |
371183fa | 1201 | layer1_read); |
e1199815 MS |
1202 | ASSERT_LE(0, ruleset_fd); |
1203 | enforce_ruleset(_metadata, ruleset_fd); | |
1204 | ASSERT_EQ(0, close(ruleset_fd)); | |
1205 | ||
1206 | /* Checks that read access is granted for file1_s1d3 with layer 1. */ | |
1207 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
1208 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1209 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | |
1210 | ||
371183fa MS |
1211 | ruleset_fd = create_ruleset(_metadata, |
1212 | LANDLOCK_ACCESS_FS_READ_FILE | | |
1213 | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
1214 | layer2_read_write); | |
e1199815 MS |
1215 | ASSERT_LE(0, ruleset_fd); |
1216 | enforce_ruleset(_metadata, ruleset_fd); | |
1217 | ASSERT_EQ(0, close(ruleset_fd)); | |
1218 | ||
1219 | /* Checks that previous access rights are unchanged with layer 2. */ | |
1220 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
1221 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1222 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | |
1223 | ||
1224 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | |
371183fa | 1225 | layer3_read); |
e1199815 MS |
1226 | ASSERT_LE(0, ruleset_fd); |
1227 | enforce_ruleset(_metadata, ruleset_fd); | |
1228 | ASSERT_EQ(0, close(ruleset_fd)); | |
1229 | ||
1230 | /* Checks that previous access rights are unchanged with layer 3. */ | |
1231 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | |
1232 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1233 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | |
1234 | ||
1235 | /* This time, denies write access for the file hierarchy. */ | |
371183fa MS |
1236 | ruleset_fd = create_ruleset(_metadata, |
1237 | LANDLOCK_ACCESS_FS_READ_FILE | | |
1238 | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
1239 | layer4_read_write); | |
e1199815 MS |
1240 | ASSERT_LE(0, ruleset_fd); |
1241 | enforce_ruleset(_metadata, ruleset_fd); | |
1242 | ASSERT_EQ(0, close(ruleset_fd)); | |
1243 | ||
1244 | /* | |
1245 | * Checks that the only change with layer 4 is that write access is | |
1246 | * denied. | |
1247 | */ | |
1248 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1249 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1250 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1251 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | |
1252 | ||
1253 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | |
371183fa | 1254 | layer5_read); |
e1199815 MS |
1255 | ASSERT_LE(0, ruleset_fd); |
1256 | enforce_ruleset(_metadata, ruleset_fd); | |
1257 | ASSERT_EQ(0, close(ruleset_fd)); | |
1258 | ||
1259 | /* Checks that previous access rights are unchanged with layer 5. */ | |
1260 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1261 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1262 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | |
1263 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1264 | ||
1265 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, | |
371183fa | 1266 | layer6_execute); |
e1199815 MS |
1267 | ASSERT_LE(0, ruleset_fd); |
1268 | enforce_ruleset(_metadata, ruleset_fd); | |
1269 | ASSERT_EQ(0, close(ruleset_fd)); | |
1270 | ||
1271 | /* Checks that previous access rights are unchanged with layer 6. */ | |
1272 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1273 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1274 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | |
1275 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1276 | ||
371183fa MS |
1277 | ruleset_fd = create_ruleset(_metadata, |
1278 | LANDLOCK_ACCESS_FS_READ_FILE | | |
1279 | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
1280 | layer7_read_write); | |
e1199815 MS |
1281 | ASSERT_LE(0, ruleset_fd); |
1282 | enforce_ruleset(_metadata, ruleset_fd); | |
1283 | ASSERT_EQ(0, close(ruleset_fd)); | |
1284 | ||
1285 | /* Checks read access is now denied with layer 7. */ | |
1286 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); | |
1287 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1288 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | |
1289 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | |
1290 | } | |
1291 | ||
1292 | TEST_F_FORK(layout1, inherit_subset) | |
1293 | { | |
1294 | const struct rule rules[] = { | |
1295 | { | |
1296 | .path = dir_s1d2, | |
1297 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 1298 | LANDLOCK_ACCESS_FS_READ_DIR, |
e1199815 | 1299 | }, |
135464f9 | 1300 | {}, |
e1199815 MS |
1301 | }; |
1302 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1303 | ||
1304 | ASSERT_LE(0, ruleset_fd); | |
1305 | enforce_ruleset(_metadata, ruleset_fd); | |
1306 | ||
1307 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1308 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1309 | ||
1310 | /* Write access is forbidden. */ | |
1311 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1312 | /* Readdir access is allowed. */ | |
1313 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1314 | ||
1315 | /* Write access is forbidden. */ | |
1316 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1317 | /* Readdir access is allowed. */ | |
1318 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1319 | ||
1320 | /* | |
1321 | * Tests shared rule extension: the following rules should not grant | |
1322 | * any new access, only remove some. Once enforced, these rules are | |
1323 | * ANDed with the previous ones. | |
1324 | */ | |
1325 | add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, | |
371183fa | 1326 | dir_s1d2); |
e1199815 MS |
1327 | /* |
1328 | * According to ruleset_fd, dir_s1d2 should now have the | |
1329 | * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE | |
1330 | * access rights (even if this directory is opened a second time). | |
1331 | * However, when enforcing this updated ruleset, the ruleset tied to | |
1332 | * the current process (i.e. its domain) will still only have the | |
1333 | * dir_s1d2 with LANDLOCK_ACCESS_FS_READ_FILE and | |
1334 | * LANDLOCK_ACCESS_FS_READ_DIR accesses, but | |
1335 | * LANDLOCK_ACCESS_FS_WRITE_FILE must not be allowed because it would | |
1336 | * be a privilege escalation. | |
1337 | */ | |
1338 | enforce_ruleset(_metadata, ruleset_fd); | |
1339 | ||
1340 | /* Same tests and results as above. */ | |
1341 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1342 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1343 | ||
1344 | /* It is still forbidden to write in file1_s1d2. */ | |
1345 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1346 | /* Readdir access is still allowed. */ | |
1347 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1348 | ||
1349 | /* It is still forbidden to write in file1_s1d3. */ | |
1350 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1351 | /* Readdir access is still allowed. */ | |
1352 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1353 | ||
1354 | /* | |
1355 | * Try to get more privileges by adding new access rights to the parent | |
1356 | * directory: dir_s1d1. | |
1357 | */ | |
1358 | add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1); | |
1359 | enforce_ruleset(_metadata, ruleset_fd); | |
1360 | ||
1361 | /* Same tests and results as above. */ | |
1362 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1363 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1364 | ||
1365 | /* It is still forbidden to write in file1_s1d2. */ | |
1366 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1367 | /* Readdir access is still allowed. */ | |
1368 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1369 | ||
1370 | /* It is still forbidden to write in file1_s1d3. */ | |
1371 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1372 | /* Readdir access is still allowed. */ | |
1373 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1374 | ||
1375 | /* | |
1376 | * Now, dir_s1d3 get a new rule tied to it, only allowing | |
1377 | * LANDLOCK_ACCESS_FS_WRITE_FILE. The (kernel internal) difference is | |
1378 | * that there was no rule tied to it before. | |
1379 | */ | |
1380 | add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, | |
371183fa | 1381 | dir_s1d3); |
e1199815 MS |
1382 | enforce_ruleset(_metadata, ruleset_fd); |
1383 | ASSERT_EQ(0, close(ruleset_fd)); | |
1384 | ||
1385 | /* | |
1386 | * Same tests and results as above, except for open(dir_s1d3) which is | |
1387 | * now denied because the new rule mask the rule previously inherited | |
1388 | * from dir_s1d2. | |
1389 | */ | |
1390 | ||
1391 | /* Same tests and results as above. */ | |
1392 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
1393 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
1394 | ||
1395 | /* It is still forbidden to write in file1_s1d2. */ | |
1396 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
1397 | /* Readdir access is still allowed. */ | |
1398 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1399 | ||
1400 | /* It is still forbidden to write in file1_s1d3. */ | |
1401 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
1402 | /* | |
1403 | * Readdir of dir_s1d3 is still allowed because of the OR policy inside | |
1404 | * the same layer. | |
1405 | */ | |
1406 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1407 | } | |
1408 | ||
1409 | TEST_F_FORK(layout1, inherit_superset) | |
1410 | { | |
1411 | const struct rule rules[] = { | |
1412 | { | |
1413 | .path = dir_s1d3, | |
1414 | .access = ACCESS_RO, | |
1415 | }, | |
135464f9 | 1416 | {}, |
e1199815 MS |
1417 | }; |
1418 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1419 | ||
1420 | ASSERT_LE(0, ruleset_fd); | |
1421 | enforce_ruleset(_metadata, ruleset_fd); | |
1422 | ||
1423 | /* Readdir access is denied for dir_s1d2. */ | |
1424 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1425 | /* Readdir access is allowed for dir_s1d3. */ | |
1426 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1427 | /* File access is allowed for file1_s1d3. */ | |
1428 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1429 | ||
1430 | /* Now dir_s1d2, parent of dir_s1d3, gets a new rule tied to it. */ | |
371183fa MS |
1431 | add_path_beneath(_metadata, ruleset_fd, |
1432 | LANDLOCK_ACCESS_FS_READ_FILE | | |
1433 | LANDLOCK_ACCESS_FS_READ_DIR, | |
1434 | dir_s1d2); | |
e1199815 MS |
1435 | enforce_ruleset(_metadata, ruleset_fd); |
1436 | ASSERT_EQ(0, close(ruleset_fd)); | |
1437 | ||
1438 | /* Readdir access is still denied for dir_s1d2. */ | |
1439 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
1440 | /* Readdir access is still allowed for dir_s1d3. */ | |
1441 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
1442 | /* File access is still allowed for file1_s1d3. */ | |
1443 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
1444 | } | |
1445 | ||
592efeb4 | 1446 | TEST_F_FORK(layout0, max_layers) |
e1199815 MS |
1447 | { |
1448 | int i, err; | |
1449 | const struct rule rules[] = { | |
1450 | { | |
592efeb4 | 1451 | .path = TMP_DIR, |
e1199815 MS |
1452 | .access = ACCESS_RO, |
1453 | }, | |
135464f9 | 1454 | {}, |
e1199815 MS |
1455 | }; |
1456 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1457 | ||
1458 | ASSERT_LE(0, ruleset_fd); | |
75c542d6 | 1459 | for (i = 0; i < 16; i++) |
e1199815 MS |
1460 | enforce_ruleset(_metadata, ruleset_fd); |
1461 | ||
1462 | for (i = 0; i < 2; i++) { | |
1463 | err = landlock_restrict_self(ruleset_fd, 0); | |
1464 | ASSERT_EQ(-1, err); | |
1465 | ASSERT_EQ(E2BIG, errno); | |
1466 | } | |
1467 | ASSERT_EQ(0, close(ruleset_fd)); | |
1468 | } | |
1469 | ||
1470 | TEST_F_FORK(layout1, empty_or_same_ruleset) | |
1471 | { | |
1472 | struct landlock_ruleset_attr ruleset_attr = {}; | |
1473 | int ruleset_fd; | |
1474 | ||
1475 | /* Tests empty handled_access_fs. */ | |
371183fa MS |
1476 | ruleset_fd = |
1477 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
e1199815 MS |
1478 | ASSERT_LE(-1, ruleset_fd); |
1479 | ASSERT_EQ(ENOMSG, errno); | |
1480 | ||
1481 | /* Enforces policy which deny read access to all files. */ | |
1482 | ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE; | |
371183fa MS |
1483 | ruleset_fd = |
1484 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
e1199815 MS |
1485 | ASSERT_LE(0, ruleset_fd); |
1486 | enforce_ruleset(_metadata, ruleset_fd); | |
1487 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
1488 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
1489 | ||
1490 | /* Nests a policy which deny read access to all directories. */ | |
1491 | ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR; | |
371183fa MS |
1492 | ruleset_fd = |
1493 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
e1199815 MS |
1494 | ASSERT_LE(0, ruleset_fd); |
1495 | enforce_ruleset(_metadata, ruleset_fd); | |
1496 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
1497 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | |
1498 | ||
1499 | /* Enforces a second time with the same ruleset. */ | |
1500 | enforce_ruleset(_metadata, ruleset_fd); | |
1501 | ASSERT_EQ(0, close(ruleset_fd)); | |
1502 | } | |
1503 | ||
1504 | TEST_F_FORK(layout1, rule_on_mountpoint) | |
1505 | { | |
1506 | const struct rule rules[] = { | |
1507 | { | |
1508 | .path = dir_s1d1, | |
1509 | .access = ACCESS_RO, | |
1510 | }, | |
1511 | { | |
1512 | /* dir_s3d2 is a mount point. */ | |
1513 | .path = dir_s3d2, | |
1514 | .access = ACCESS_RO, | |
1515 | }, | |
135464f9 | 1516 | {}, |
e1199815 MS |
1517 | }; |
1518 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1519 | ||
1520 | ASSERT_LE(0, ruleset_fd); | |
1521 | enforce_ruleset(_metadata, ruleset_fd); | |
1522 | ASSERT_EQ(0, close(ruleset_fd)); | |
1523 | ||
1524 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
1525 | ||
1526 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); | |
1527 | ||
1528 | ASSERT_EQ(EACCES, test_open(dir_s3d1, O_RDONLY)); | |
1529 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | |
1530 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | |
1531 | } | |
1532 | ||
1533 | TEST_F_FORK(layout1, rule_over_mountpoint) | |
1534 | { | |
1535 | const struct rule rules[] = { | |
1536 | { | |
1537 | .path = dir_s1d1, | |
1538 | .access = ACCESS_RO, | |
1539 | }, | |
1540 | { | |
1541 | /* dir_s3d2 is a mount point. */ | |
1542 | .path = dir_s3d1, | |
1543 | .access = ACCESS_RO, | |
1544 | }, | |
135464f9 | 1545 | {}, |
e1199815 MS |
1546 | }; |
1547 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1548 | ||
1549 | ASSERT_LE(0, ruleset_fd); | |
1550 | enforce_ruleset(_metadata, ruleset_fd); | |
1551 | ASSERT_EQ(0, close(ruleset_fd)); | |
1552 | ||
1553 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
1554 | ||
1555 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); | |
1556 | ||
1557 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | |
1558 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | |
1559 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | |
1560 | } | |
1561 | ||
1562 | /* | |
1563 | * This test verifies that we can apply a landlock rule on the root directory | |
1564 | * (which might require special handling). | |
1565 | */ | |
1566 | TEST_F_FORK(layout1, rule_over_root_allow_then_deny) | |
1567 | { | |
1568 | struct rule rules[] = { | |
1569 | { | |
1570 | .path = "/", | |
1571 | .access = ACCESS_RO, | |
1572 | }, | |
135464f9 | 1573 | {}, |
e1199815 MS |
1574 | }; |
1575 | int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1576 | ||
1577 | ASSERT_LE(0, ruleset_fd); | |
1578 | enforce_ruleset(_metadata, ruleset_fd); | |
1579 | ASSERT_EQ(0, close(ruleset_fd)); | |
1580 | ||
1581 | /* Checks allowed access. */ | |
1582 | ASSERT_EQ(0, test_open("/", O_RDONLY)); | |
1583 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
1584 | ||
1585 | rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE; | |
1586 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1587 | ASSERT_LE(0, ruleset_fd); | |
1588 | enforce_ruleset(_metadata, ruleset_fd); | |
1589 | ASSERT_EQ(0, close(ruleset_fd)); | |
1590 | ||
1591 | /* Checks denied access (on a directory). */ | |
1592 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | |
1593 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | |
1594 | } | |
1595 | ||
1596 | TEST_F_FORK(layout1, rule_over_root_deny) | |
1597 | { | |
1598 | const struct rule rules[] = { | |
1599 | { | |
1600 | .path = "/", | |
1601 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
1602 | }, | |
135464f9 | 1603 | {}, |
e1199815 MS |
1604 | }; |
1605 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1606 | ||
1607 | ASSERT_LE(0, ruleset_fd); | |
1608 | enforce_ruleset(_metadata, ruleset_fd); | |
1609 | ASSERT_EQ(0, close(ruleset_fd)); | |
1610 | ||
1611 | /* Checks denied access (on a directory). */ | |
1612 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | |
1613 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | |
1614 | } | |
1615 | ||
1616 | TEST_F_FORK(layout1, rule_inside_mount_ns) | |
1617 | { | |
1618 | const struct rule rules[] = { | |
1619 | { | |
1620 | .path = "s3d3", | |
1621 | .access = ACCESS_RO, | |
1622 | }, | |
135464f9 | 1623 | {}, |
e1199815 MS |
1624 | }; |
1625 | int ruleset_fd; | |
1626 | ||
1627 | set_cap(_metadata, CAP_SYS_ADMIN); | |
87129ef1 | 1628 | ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)) |
371183fa | 1629 | { |
e1199815 MS |
1630 | TH_LOG("Failed to pivot root: %s", strerror(errno)); |
1631 | }; | |
1632 | ASSERT_EQ(0, chdir("/")); | |
1633 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1634 | ||
1635 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1636 | ASSERT_LE(0, ruleset_fd); | |
1637 | enforce_ruleset(_metadata, ruleset_fd); | |
1638 | ASSERT_EQ(0, close(ruleset_fd)); | |
1639 | ||
1640 | ASSERT_EQ(0, test_open("s3d3", O_RDONLY)); | |
1641 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | |
1642 | } | |
1643 | ||
1644 | TEST_F_FORK(layout1, mount_and_pivot) | |
1645 | { | |
1646 | const struct rule rules[] = { | |
1647 | { | |
1648 | .path = dir_s3d2, | |
1649 | .access = ACCESS_RO, | |
1650 | }, | |
135464f9 | 1651 | {}, |
e1199815 MS |
1652 | }; |
1653 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1654 | ||
1655 | ASSERT_LE(0, ruleset_fd); | |
1656 | enforce_ruleset(_metadata, ruleset_fd); | |
1657 | ASSERT_EQ(0, close(ruleset_fd)); | |
1658 | ||
1659 | set_cap(_metadata, CAP_SYS_ADMIN); | |
1660 | ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); | |
1661 | ASSERT_EQ(EPERM, errno); | |
87129ef1 | 1662 | ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)); |
e1199815 MS |
1663 | ASSERT_EQ(EPERM, errno); |
1664 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1665 | } | |
1666 | ||
1667 | TEST_F_FORK(layout1, move_mount) | |
1668 | { | |
1669 | const struct rule rules[] = { | |
1670 | { | |
1671 | .path = dir_s3d2, | |
1672 | .access = ACCESS_RO, | |
1673 | }, | |
135464f9 | 1674 | {}, |
e1199815 MS |
1675 | }; |
1676 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1677 | ||
1678 | ASSERT_LE(0, ruleset_fd); | |
1679 | ||
1680 | set_cap(_metadata, CAP_SYS_ADMIN); | |
87129ef1 | 1681 | ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, |
371183fa MS |
1682 | dir_s1d2, 0)) |
1683 | { | |
e1199815 MS |
1684 | TH_LOG("Failed to move mount: %s", strerror(errno)); |
1685 | } | |
1686 | ||
87129ef1 | 1687 | ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, |
371183fa | 1688 | dir_s3d2, 0)); |
e1199815 MS |
1689 | clear_cap(_metadata, CAP_SYS_ADMIN); |
1690 | ||
1691 | enforce_ruleset(_metadata, ruleset_fd); | |
1692 | ASSERT_EQ(0, close(ruleset_fd)); | |
1693 | ||
1694 | set_cap(_metadata, CAP_SYS_ADMIN); | |
87129ef1 | 1695 | ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, |
371183fa | 1696 | dir_s1d2, 0)); |
e1199815 MS |
1697 | ASSERT_EQ(EPERM, errno); |
1698 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1699 | } | |
1700 | ||
f12f8f84 MS |
1701 | TEST_F_FORK(layout1, topology_changes_with_net_only) |
1702 | { | |
1703 | const struct landlock_ruleset_attr ruleset_net = { | |
1704 | .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | | |
1705 | LANDLOCK_ACCESS_NET_CONNECT_TCP, | |
1706 | }; | |
1707 | int ruleset_fd; | |
1708 | ||
1709 | /* Add network restrictions. */ | |
1710 | ruleset_fd = | |
1711 | landlock_create_ruleset(&ruleset_net, sizeof(ruleset_net), 0); | |
1712 | ASSERT_LE(0, ruleset_fd); | |
1713 | enforce_ruleset(_metadata, ruleset_fd); | |
1714 | ASSERT_EQ(0, close(ruleset_fd)); | |
1715 | ||
1716 | /* Mount, remount, move_mount, umount, and pivot_root checks. */ | |
1717 | set_cap(_metadata, CAP_SYS_ADMIN); | |
1718 | ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s1d2)); | |
1719 | ASSERT_EQ(0, mount(NULL, dir_s1d2, NULL, MS_PRIVATE | MS_REC, NULL)); | |
1720 | ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, | |
1721 | dir_s2d2, 0)); | |
1722 | ASSERT_EQ(0, umount(dir_s2d2)); | |
1723 | ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)); | |
1724 | ASSERT_EQ(0, chdir("/")); | |
1725 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1726 | } | |
1727 | ||
1728 | TEST_F_FORK(layout1, topology_changes_with_net_and_fs) | |
1729 | { | |
1730 | const struct landlock_ruleset_attr ruleset_net_fs = { | |
1731 | .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | | |
1732 | LANDLOCK_ACCESS_NET_CONNECT_TCP, | |
1733 | .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, | |
1734 | }; | |
1735 | int ruleset_fd; | |
1736 | ||
1737 | /* Add network and filesystem restrictions. */ | |
1738 | ruleset_fd = landlock_create_ruleset(&ruleset_net_fs, | |
1739 | sizeof(ruleset_net_fs), 0); | |
1740 | ASSERT_LE(0, ruleset_fd); | |
1741 | enforce_ruleset(_metadata, ruleset_fd); | |
1742 | ASSERT_EQ(0, close(ruleset_fd)); | |
1743 | ||
1744 | /* Mount, remount, move_mount, umount, and pivot_root checks. */ | |
1745 | set_cap(_metadata, CAP_SYS_ADMIN); | |
1746 | ASSERT_EQ(-1, mount_opt(&mnt_tmp, dir_s1d2)); | |
1747 | ASSERT_EQ(EPERM, errno); | |
1748 | ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_PRIVATE | MS_REC, NULL)); | |
1749 | ASSERT_EQ(EPERM, errno); | |
1750 | ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, | |
1751 | dir_s2d2, 0)); | |
1752 | ASSERT_EQ(EPERM, errno); | |
1753 | ASSERT_EQ(-1, umount(dir_s3d2)); | |
1754 | ASSERT_EQ(EPERM, errno); | |
1755 | ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)); | |
1756 | ASSERT_EQ(EPERM, errno); | |
1757 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1758 | } | |
1759 | ||
e1199815 MS |
1760 | TEST_F_FORK(layout1, release_inodes) |
1761 | { | |
1762 | const struct rule rules[] = { | |
1763 | { | |
1764 | .path = dir_s1d1, | |
1765 | .access = ACCESS_RO, | |
1766 | }, | |
1767 | { | |
1768 | .path = dir_s3d2, | |
1769 | .access = ACCESS_RO, | |
1770 | }, | |
1771 | { | |
1772 | .path = dir_s3d3, | |
1773 | .access = ACCESS_RO, | |
1774 | }, | |
135464f9 | 1775 | {}, |
e1199815 MS |
1776 | }; |
1777 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | |
1778 | ||
1779 | ASSERT_LE(0, ruleset_fd); | |
1780 | /* Unmount a file hierarchy while it is being used by a ruleset. */ | |
1781 | set_cap(_metadata, CAP_SYS_ADMIN); | |
1782 | ASSERT_EQ(0, umount(dir_s3d2)); | |
1783 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
1784 | ||
1785 | enforce_ruleset(_metadata, ruleset_fd); | |
1786 | ASSERT_EQ(0, close(ruleset_fd)); | |
1787 | ||
1788 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | |
1789 | ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY)); | |
1790 | /* This dir_s3d3 would not be allowed and does not exist anyway. */ | |
1791 | ASSERT_EQ(ENOENT, test_open(dir_s3d3, O_RDONLY)); | |
1792 | } | |
1793 | ||
1794 | enum relative_access { | |
1795 | REL_OPEN, | |
1796 | REL_CHDIR, | |
1797 | REL_CHROOT_ONLY, | |
1798 | REL_CHROOT_CHDIR, | |
1799 | }; | |
1800 | ||
1801 | static void test_relative_path(struct __test_metadata *const _metadata, | |
371183fa | 1802 | const enum relative_access rel) |
e1199815 MS |
1803 | { |
1804 | /* | |
1805 | * Common layer to check that chroot doesn't ignore it (i.e. a chroot | |
1806 | * is not a disconnected root directory). | |
1807 | */ | |
1808 | const struct rule layer1_base[] = { | |
1809 | { | |
1810 | .path = TMP_DIR, | |
1811 | .access = ACCESS_RO, | |
1812 | }, | |
135464f9 | 1813 | {}, |
e1199815 MS |
1814 | }; |
1815 | const struct rule layer2_subs[] = { | |
1816 | { | |
1817 | .path = dir_s1d2, | |
1818 | .access = ACCESS_RO, | |
1819 | }, | |
1820 | { | |
1821 | .path = dir_s2d2, | |
1822 | .access = ACCESS_RO, | |
1823 | }, | |
135464f9 | 1824 | {}, |
e1199815 MS |
1825 | }; |
1826 | int dirfd, ruleset_fd; | |
1827 | ||
1828 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); | |
1829 | ASSERT_LE(0, ruleset_fd); | |
1830 | enforce_ruleset(_metadata, ruleset_fd); | |
1831 | ASSERT_EQ(0, close(ruleset_fd)); | |
1832 | ||
1833 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs); | |
1834 | ||
1835 | ASSERT_LE(0, ruleset_fd); | |
1836 | switch (rel) { | |
1837 | case REL_OPEN: | |
1838 | case REL_CHDIR: | |
1839 | break; | |
1840 | case REL_CHROOT_ONLY: | |
1841 | ASSERT_EQ(0, chdir(dir_s2d2)); | |
1842 | break; | |
1843 | case REL_CHROOT_CHDIR: | |
1844 | ASSERT_EQ(0, chdir(dir_s1d2)); | |
1845 | break; | |
1846 | default: | |
1847 | ASSERT_TRUE(false); | |
1848 | return; | |
1849 | } | |
1850 | ||
1851 | set_cap(_metadata, CAP_SYS_CHROOT); | |
1852 | enforce_ruleset(_metadata, ruleset_fd); | |
1853 | ||
1854 | switch (rel) { | |
1855 | case REL_OPEN: | |
1856 | dirfd = open(dir_s1d2, O_DIRECTORY); | |
1857 | ASSERT_LE(0, dirfd); | |
1858 | break; | |
1859 | case REL_CHDIR: | |
1860 | ASSERT_EQ(0, chdir(dir_s1d2)); | |
1861 | dirfd = AT_FDCWD; | |
1862 | break; | |
1863 | case REL_CHROOT_ONLY: | |
1864 | /* Do chroot into dir_s1d2 (relative to dir_s2d2). */ | |
371183fa MS |
1865 | ASSERT_EQ(0, chroot("../../s1d1/s1d2")) |
1866 | { | |
e1199815 MS |
1867 | TH_LOG("Failed to chroot: %s", strerror(errno)); |
1868 | } | |
1869 | dirfd = AT_FDCWD; | |
1870 | break; | |
1871 | case REL_CHROOT_CHDIR: | |
1872 | /* Do chroot into dir_s1d2. */ | |
371183fa MS |
1873 | ASSERT_EQ(0, chroot(".")) |
1874 | { | |
e1199815 MS |
1875 | TH_LOG("Failed to chroot: %s", strerror(errno)); |
1876 | } | |
1877 | dirfd = AT_FDCWD; | |
1878 | break; | |
1879 | } | |
1880 | ||
1881 | ASSERT_EQ((rel == REL_CHROOT_CHDIR) ? 0 : EACCES, | |
371183fa | 1882 | test_open_rel(dirfd, "..", O_RDONLY)); |
e1199815 MS |
1883 | ASSERT_EQ(0, test_open_rel(dirfd, ".", O_RDONLY)); |
1884 | ||
1885 | if (rel == REL_CHROOT_ONLY) { | |
1886 | /* The current directory is dir_s2d2. */ | |
1887 | ASSERT_EQ(0, test_open_rel(dirfd, "./s2d3", O_RDONLY)); | |
1888 | } else { | |
1889 | /* The current directory is dir_s1d2. */ | |
1890 | ASSERT_EQ(0, test_open_rel(dirfd, "./s1d3", O_RDONLY)); | |
1891 | } | |
1892 | ||
1893 | if (rel == REL_CHROOT_ONLY || rel == REL_CHROOT_CHDIR) { | |
1894 | /* Checks the root dir_s1d2. */ | |
1895 | ASSERT_EQ(0, test_open_rel(dirfd, "/..", O_RDONLY)); | |
1896 | ASSERT_EQ(0, test_open_rel(dirfd, "/", O_RDONLY)); | |
1897 | ASSERT_EQ(0, test_open_rel(dirfd, "/f1", O_RDONLY)); | |
1898 | ASSERT_EQ(0, test_open_rel(dirfd, "/s1d3", O_RDONLY)); | |
1899 | } | |
1900 | ||
1901 | if (rel != REL_CHROOT_CHDIR) { | |
1902 | ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s1d1", O_RDONLY)); | |
1903 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2", O_RDONLY)); | |
371183fa MS |
1904 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3", |
1905 | O_RDONLY)); | |
e1199815 MS |
1906 | |
1907 | ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s2d1", O_RDONLY)); | |
1908 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2", O_RDONLY)); | |
371183fa MS |
1909 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3", |
1910 | O_RDONLY)); | |
e1199815 MS |
1911 | } |
1912 | ||
1913 | if (rel == REL_OPEN) | |
1914 | ASSERT_EQ(0, close(dirfd)); | |
1915 | ASSERT_EQ(0, close(ruleset_fd)); | |
1916 | } | |
1917 | ||
1918 | TEST_F_FORK(layout1, relative_open) | |
1919 | { | |
1920 | test_relative_path(_metadata, REL_OPEN); | |
1921 | } | |
1922 | ||
1923 | TEST_F_FORK(layout1, relative_chdir) | |
1924 | { | |
1925 | test_relative_path(_metadata, REL_CHDIR); | |
1926 | } | |
1927 | ||
1928 | TEST_F_FORK(layout1, relative_chroot_only) | |
1929 | { | |
1930 | test_relative_path(_metadata, REL_CHROOT_ONLY); | |
1931 | } | |
1932 | ||
1933 | TEST_F_FORK(layout1, relative_chroot_chdir) | |
1934 | { | |
1935 | test_relative_path(_metadata, REL_CHROOT_CHDIR); | |
1936 | } | |
1937 | ||
1938 | static void copy_binary(struct __test_metadata *const _metadata, | |
371183fa | 1939 | const char *const dst_path) |
e1199815 MS |
1940 | { |
1941 | int dst_fd, src_fd; | |
1942 | struct stat statbuf; | |
1943 | ||
1944 | dst_fd = open(dst_path, O_WRONLY | O_TRUNC | O_CLOEXEC); | |
371183fa MS |
1945 | ASSERT_LE(0, dst_fd) |
1946 | { | |
1947 | TH_LOG("Failed to open \"%s\": %s", dst_path, strerror(errno)); | |
e1199815 MS |
1948 | } |
1949 | src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC); | |
371183fa MS |
1950 | ASSERT_LE(0, src_fd) |
1951 | { | |
e1199815 | 1952 | TH_LOG("Failed to open \"" BINARY_PATH "\": %s", |
371183fa | 1953 | strerror(errno)); |
e1199815 MS |
1954 | } |
1955 | ASSERT_EQ(0, fstat(src_fd, &statbuf)); | |
371183fa MS |
1956 | ASSERT_EQ(statbuf.st_size, |
1957 | sendfile(dst_fd, src_fd, 0, statbuf.st_size)); | |
e1199815 MS |
1958 | ASSERT_EQ(0, close(src_fd)); |
1959 | ASSERT_EQ(0, close(dst_fd)); | |
1960 | } | |
1961 | ||
371183fa MS |
1962 | static void test_execute(struct __test_metadata *const _metadata, const int err, |
1963 | const char *const path) | |
e1199815 MS |
1964 | { |
1965 | int status; | |
371183fa | 1966 | char *const argv[] = { (char *)path, NULL }; |
e1199815 MS |
1967 | const pid_t child = fork(); |
1968 | ||
1969 | ASSERT_LE(0, child); | |
1970 | if (child == 0) { | |
371183fa MS |
1971 | ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL)) |
1972 | { | |
e1199815 | 1973 | TH_LOG("Failed to execute \"%s\": %s", path, |
371183fa | 1974 | strerror(errno)); |
e1199815 MS |
1975 | }; |
1976 | ASSERT_EQ(err, errno); | |
69fe8ec4 | 1977 | _exit(__test_passed(_metadata) ? 2 : 1); |
e1199815 MS |
1978 | return; |
1979 | } | |
1980 | ASSERT_EQ(child, waitpid(child, &status, 0)); | |
1981 | ASSERT_EQ(1, WIFEXITED(status)); | |
371183fa MS |
1982 | ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status)) |
1983 | { | |
e1199815 | 1984 | TH_LOG("Unexpected return code for \"%s\": %s", path, |
371183fa | 1985 | strerror(errno)); |
e1199815 MS |
1986 | }; |
1987 | } | |
1988 | ||
1989 | TEST_F_FORK(layout1, execute) | |
1990 | { | |
1991 | const struct rule rules[] = { | |
1992 | { | |
1993 | .path = dir_s1d2, | |
1994 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
1995 | }, | |
135464f9 | 1996 | {}, |
e1199815 | 1997 | }; |
371183fa MS |
1998 | const int ruleset_fd = |
1999 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
2000 | |
2001 | ASSERT_LE(0, ruleset_fd); | |
2002 | copy_binary(_metadata, file1_s1d1); | |
2003 | copy_binary(_metadata, file1_s1d2); | |
2004 | copy_binary(_metadata, file1_s1d3); | |
2005 | ||
2006 | enforce_ruleset(_metadata, ruleset_fd); | |
2007 | ASSERT_EQ(0, close(ruleset_fd)); | |
2008 | ||
2009 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
2010 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | |
2011 | test_execute(_metadata, EACCES, file1_s1d1); | |
2012 | ||
2013 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | |
2014 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
2015 | test_execute(_metadata, 0, file1_s1d2); | |
2016 | ||
2017 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | |
2018 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
2019 | test_execute(_metadata, 0, file1_s1d3); | |
2020 | } | |
2021 | ||
2022 | TEST_F_FORK(layout1, link) | |
2023 | { | |
6a1bdd4a | 2024 | const struct rule layer1[] = { |
e1199815 MS |
2025 | { |
2026 | .path = dir_s1d2, | |
2027 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2028 | }, | |
135464f9 | 2029 | {}, |
e1199815 | 2030 | }; |
6a1bdd4a MS |
2031 | const struct rule layer2[] = { |
2032 | { | |
2033 | .path = dir_s1d3, | |
2034 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2035 | }, | |
2036 | {}, | |
2037 | }; | |
2038 | int ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1); | |
e1199815 MS |
2039 | |
2040 | ASSERT_LE(0, ruleset_fd); | |
2041 | ||
2042 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
2043 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2044 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2045 | ||
2046 | enforce_ruleset(_metadata, ruleset_fd); | |
2047 | ASSERT_EQ(0, close(ruleset_fd)); | |
2048 | ||
2049 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | |
2050 | ASSERT_EQ(EACCES, errno); | |
6a1bdd4a | 2051 | |
e1199815 MS |
2052 | /* Denies linking because of reparenting. */ |
2053 | ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); | |
2054 | ASSERT_EQ(EXDEV, errno); | |
2055 | ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); | |
2056 | ASSERT_EQ(EXDEV, errno); | |
6a1bdd4a MS |
2057 | ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2)); |
2058 | ASSERT_EQ(EXDEV, errno); | |
e1199815 MS |
2059 | |
2060 | ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); | |
2061 | ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); | |
6a1bdd4a MS |
2062 | |
2063 | /* Prepares for next unlinks. */ | |
2064 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
2065 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
2066 | ||
2067 | ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2); | |
2068 | ASSERT_LE(0, ruleset_fd); | |
2069 | enforce_ruleset(_metadata, ruleset_fd); | |
2070 | ASSERT_EQ(0, close(ruleset_fd)); | |
2071 | ||
2072 | /* Checks that linkind doesn't require the ability to delete a file. */ | |
2073 | ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); | |
2074 | ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); | |
e1199815 MS |
2075 | } |
2076 | ||
55e55920 MS |
2077 | static int test_rename(const char *const oldpath, const char *const newpath) |
2078 | { | |
2079 | if (rename(oldpath, newpath)) | |
2080 | return errno; | |
2081 | return 0; | |
2082 | } | |
2083 | ||
2084 | static int test_exchange(const char *const oldpath, const char *const newpath) | |
2085 | { | |
2086 | if (renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE)) | |
2087 | return errno; | |
2088 | return 0; | |
2089 | } | |
2090 | ||
e1199815 MS |
2091 | TEST_F_FORK(layout1, rename_file) |
2092 | { | |
2093 | const struct rule rules[] = { | |
2094 | { | |
2095 | .path = dir_s1d3, | |
2096 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2097 | }, | |
2098 | { | |
2099 | .path = dir_s2d2, | |
2100 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2101 | }, | |
135464f9 | 2102 | {}, |
e1199815 | 2103 | }; |
371183fa MS |
2104 | const int ruleset_fd = |
2105 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
2106 | |
2107 | ASSERT_LE(0, ruleset_fd); | |
2108 | ||
e1199815 MS |
2109 | ASSERT_EQ(0, unlink(file1_s1d2)); |
2110 | ||
2111 | enforce_ruleset(_metadata, ruleset_fd); | |
2112 | ASSERT_EQ(0, close(ruleset_fd)); | |
2113 | ||
2114 | /* | |
2115 | * Tries to replace a file, from a directory that allows file removal, | |
2116 | * but to a different directory (which also allows file removal). | |
2117 | */ | |
2118 | ASSERT_EQ(-1, rename(file1_s2d3, file1_s1d3)); | |
2119 | ASSERT_EQ(EXDEV, errno); | |
2120 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d3, | |
2121 | RENAME_EXCHANGE)); | |
2122 | ASSERT_EQ(EXDEV, errno); | |
2123 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3, | |
2124 | RENAME_EXCHANGE)); | |
2125 | ASSERT_EQ(EXDEV, errno); | |
2126 | ||
2127 | /* | |
2128 | * Tries to replace a file, from a directory that denies file removal, | |
2129 | * to a different directory (which allows file removal). | |
2130 | */ | |
2131 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); | |
55e55920 | 2132 | ASSERT_EQ(EACCES, errno); |
e1199815 MS |
2133 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3, |
2134 | RENAME_EXCHANGE)); | |
55e55920 | 2135 | ASSERT_EQ(EACCES, errno); |
e1199815 MS |
2136 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s1d3, |
2137 | RENAME_EXCHANGE)); | |
2138 | ASSERT_EQ(EXDEV, errno); | |
2139 | ||
2140 | /* Exchanges files and directories that partially allow removal. */ | |
2141 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s2d1, | |
2142 | RENAME_EXCHANGE)); | |
2143 | ASSERT_EQ(EACCES, errno); | |
6a1bdd4a MS |
2144 | /* Checks that file1_s2d1 cannot be removed (instead of ENOTDIR). */ |
2145 | ASSERT_EQ(-1, rename(dir_s2d2, file1_s2d1)); | |
2146 | ASSERT_EQ(EACCES, errno); | |
e1199815 MS |
2147 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2, |
2148 | RENAME_EXCHANGE)); | |
2149 | ASSERT_EQ(EACCES, errno); | |
6a1bdd4a MS |
2150 | /* Checks that file1_s1d1 cannot be removed (instead of EISDIR). */ |
2151 | ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2)); | |
2152 | ASSERT_EQ(EACCES, errno); | |
e1199815 MS |
2153 | |
2154 | /* Renames files with different parents. */ | |
2155 | ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); | |
2156 | ASSERT_EQ(EXDEV, errno); | |
2157 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2158 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); | |
55e55920 | 2159 | ASSERT_EQ(EACCES, errno); |
e1199815 MS |
2160 | |
2161 | /* Exchanges and renames files with same parent. */ | |
2162 | ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3, | |
371183fa | 2163 | RENAME_EXCHANGE)); |
e1199815 MS |
2164 | ASSERT_EQ(0, rename(file2_s2d3, file1_s2d3)); |
2165 | ||
2166 | /* Exchanges files and directories with same parent, twice. */ | |
2167 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, | |
371183fa | 2168 | RENAME_EXCHANGE)); |
e1199815 | 2169 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, |
371183fa | 2170 | RENAME_EXCHANGE)); |
e1199815 MS |
2171 | } |
2172 | ||
2173 | TEST_F_FORK(layout1, rename_dir) | |
2174 | { | |
2175 | const struct rule rules[] = { | |
2176 | { | |
2177 | .path = dir_s1d2, | |
2178 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | |
2179 | }, | |
2180 | { | |
2181 | .path = dir_s2d1, | |
2182 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | |
2183 | }, | |
135464f9 | 2184 | {}, |
e1199815 | 2185 | }; |
371183fa MS |
2186 | const int ruleset_fd = |
2187 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
2188 | |
2189 | ASSERT_LE(0, ruleset_fd); | |
2190 | ||
2191 | /* Empties dir_s1d3 to allow renaming. */ | |
2192 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2193 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
2194 | ||
2195 | enforce_ruleset(_metadata, ruleset_fd); | |
2196 | ASSERT_EQ(0, close(ruleset_fd)); | |
2197 | ||
2198 | /* Exchanges and renames directory to a different parent. */ | |
2199 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, | |
2200 | RENAME_EXCHANGE)); | |
2201 | ASSERT_EQ(EXDEV, errno); | |
2202 | ASSERT_EQ(-1, rename(dir_s2d3, dir_s1d3)); | |
2203 | ASSERT_EQ(EXDEV, errno); | |
2204 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, | |
2205 | RENAME_EXCHANGE)); | |
2206 | ASSERT_EQ(EXDEV, errno); | |
2207 | ||
2208 | /* | |
2209 | * Exchanges directory to the same parent, which doesn't allow | |
2210 | * directory removal. | |
2211 | */ | |
2212 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d1, AT_FDCWD, dir_s2d1, | |
2213 | RENAME_EXCHANGE)); | |
2214 | ASSERT_EQ(EACCES, errno); | |
6a1bdd4a MS |
2215 | /* Checks that dir_s1d2 cannot be removed (instead of ENOTDIR). */ |
2216 | ASSERT_EQ(-1, rename(dir_s1d2, file1_s1d1)); | |
2217 | ASSERT_EQ(EACCES, errno); | |
e1199815 MS |
2218 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2, |
2219 | RENAME_EXCHANGE)); | |
2220 | ASSERT_EQ(EACCES, errno); | |
6a1bdd4a MS |
2221 | /* Checks that dir_s1d2 cannot be removed (instead of EISDIR). */ |
2222 | ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2)); | |
2223 | ASSERT_EQ(EACCES, errno); | |
e1199815 MS |
2224 | |
2225 | /* | |
2226 | * Exchanges and renames directory to the same parent, which allows | |
2227 | * directory removal. | |
2228 | */ | |
2229 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s1d2, | |
371183fa | 2230 | RENAME_EXCHANGE)); |
e1199815 MS |
2231 | ASSERT_EQ(0, unlink(dir_s1d3)); |
2232 | ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); | |
2233 | ASSERT_EQ(0, rename(file1_s1d2, dir_s1d3)); | |
2234 | ASSERT_EQ(0, rmdir(dir_s1d3)); | |
2235 | } | |
2236 | ||
f4056b92 MS |
2237 | TEST_F_FORK(layout1, reparent_refer) |
2238 | { | |
2239 | const struct rule layer1[] = { | |
2240 | { | |
2241 | .path = dir_s1d2, | |
2242 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2243 | }, | |
2244 | { | |
2245 | .path = dir_s2d2, | |
2246 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2247 | }, | |
2248 | {}, | |
2249 | }; | |
2250 | int ruleset_fd = | |
2251 | create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); | |
2252 | ||
2253 | ASSERT_LE(0, ruleset_fd); | |
2254 | enforce_ruleset(_metadata, ruleset_fd); | |
2255 | ASSERT_EQ(0, close(ruleset_fd)); | |
2256 | ||
2257 | ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1)); | |
2258 | ASSERT_EQ(EXDEV, errno); | |
2259 | ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2)); | |
2260 | ASSERT_EQ(EXDEV, errno); | |
2261 | ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3)); | |
2262 | ASSERT_EQ(EXDEV, errno); | |
2263 | ||
2264 | ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d1)); | |
2265 | ASSERT_EQ(EXDEV, errno); | |
2266 | ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d2)); | |
2267 | ASSERT_EQ(EXDEV, errno); | |
2268 | /* | |
2269 | * Moving should only be allowed when the source and the destination | |
2270 | * parent directory have REFER. | |
2271 | */ | |
2272 | ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3)); | |
2273 | ASSERT_EQ(ENOTEMPTY, errno); | |
2274 | ASSERT_EQ(0, unlink(file1_s2d3)); | |
2275 | ASSERT_EQ(0, unlink(file2_s2d3)); | |
2276 | ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3)); | |
2277 | } | |
2278 | ||
55e55920 MS |
2279 | /* Checks renames beneath dir_s1d1. */ |
2280 | static void refer_denied_by_default(struct __test_metadata *const _metadata, | |
2281 | const struct rule layer1[], | |
2282 | const int layer1_err, | |
2283 | const struct rule layer2[]) | |
2284 | { | |
2285 | int ruleset_fd; | |
2286 | ||
2287 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2288 | ||
2289 | ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1); | |
2290 | ASSERT_LE(0, ruleset_fd); | |
2291 | enforce_ruleset(_metadata, ruleset_fd); | |
2292 | ASSERT_EQ(0, close(ruleset_fd)); | |
2293 | ||
2294 | /* | |
2295 | * If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to | |
2296 | * layer1_err), then it allows some different-parent renames and links. | |
2297 | */ | |
2298 | ASSERT_EQ(layer1_err, test_rename(file1_s1d1, file1_s1d2)); | |
2299 | if (layer1_err == 0) | |
2300 | ASSERT_EQ(layer1_err, test_rename(file1_s1d2, file1_s1d1)); | |
2301 | ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2)); | |
2302 | ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1)); | |
2303 | ||
2304 | ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2); | |
2305 | ASSERT_LE(0, ruleset_fd); | |
2306 | enforce_ruleset(_metadata, ruleset_fd); | |
2307 | ASSERT_EQ(0, close(ruleset_fd)); | |
2308 | ||
2309 | /* | |
2310 | * Now, either the first or the second layer does not handle | |
2311 | * LANDLOCK_ACCESS_FS_REFER, which means that any different-parent | |
2312 | * renames and links are denied, thus making the layer handling | |
2313 | * LANDLOCK_ACCESS_FS_REFER null and void. | |
2314 | */ | |
2315 | ASSERT_EQ(EXDEV, test_rename(file1_s1d1, file1_s1d2)); | |
2316 | ASSERT_EQ(EXDEV, test_exchange(file2_s1d1, file2_s1d2)); | |
2317 | ASSERT_EQ(EXDEV, test_exchange(file2_s1d2, file2_s1d1)); | |
2318 | } | |
2319 | ||
2320 | const struct rule layer_dir_s1d1_refer[] = { | |
2321 | { | |
2322 | .path = dir_s1d1, | |
2323 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2324 | }, | |
2325 | {}, | |
2326 | }; | |
2327 | ||
2328 | const struct rule layer_dir_s1d1_execute[] = { | |
2329 | { | |
2330 | /* Matches a parent directory. */ | |
2331 | .path = dir_s1d1, | |
2332 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
2333 | }, | |
2334 | {}, | |
2335 | }; | |
2336 | ||
2337 | const struct rule layer_dir_s2d1_execute[] = { | |
2338 | { | |
2339 | /* Does not match a parent directory. */ | |
2340 | .path = dir_s2d1, | |
2341 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
2342 | }, | |
2343 | {}, | |
2344 | }; | |
2345 | ||
2346 | /* | |
2347 | * Tests precedence over renames: denied by default for different parent | |
2348 | * directories, *with* a rule matching a parent directory, but not directly | |
2349 | * denying access (with MAKE_REG nor REMOVE). | |
2350 | */ | |
2351 | TEST_F_FORK(layout1, refer_denied_by_default1) | |
2352 | { | |
2353 | refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0, | |
2354 | layer_dir_s1d1_execute); | |
2355 | } | |
2356 | ||
2357 | /* | |
2358 | * Same test but this time turning around the ABI version order: the first | |
2359 | * layer does not handle LANDLOCK_ACCESS_FS_REFER. | |
2360 | */ | |
2361 | TEST_F_FORK(layout1, refer_denied_by_default2) | |
2362 | { | |
2363 | refer_denied_by_default(_metadata, layer_dir_s1d1_execute, EXDEV, | |
2364 | layer_dir_s1d1_refer); | |
2365 | } | |
2366 | ||
2367 | /* | |
2368 | * Tests precedence over renames: denied by default for different parent | |
2369 | * directories, *without* a rule matching a parent directory, but not directly | |
2370 | * denying access (with MAKE_REG nor REMOVE). | |
2371 | */ | |
2372 | TEST_F_FORK(layout1, refer_denied_by_default3) | |
2373 | { | |
2374 | refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0, | |
2375 | layer_dir_s2d1_execute); | |
2376 | } | |
2377 | ||
2378 | /* | |
2379 | * Same test but this time turning around the ABI version order: the first | |
2380 | * layer does not handle LANDLOCK_ACCESS_FS_REFER. | |
2381 | */ | |
2382 | TEST_F_FORK(layout1, refer_denied_by_default4) | |
2383 | { | |
2384 | refer_denied_by_default(_metadata, layer_dir_s2d1_execute, EXDEV, | |
2385 | layer_dir_s1d1_refer); | |
2386 | } | |
2387 | ||
f4056b92 MS |
2388 | TEST_F_FORK(layout1, reparent_link) |
2389 | { | |
2390 | const struct rule layer1[] = { | |
2391 | { | |
2392 | .path = dir_s1d2, | |
2393 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2394 | }, | |
2395 | { | |
2396 | .path = dir_s1d3, | |
2397 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2398 | }, | |
2399 | { | |
2400 | .path = dir_s2d2, | |
2401 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2402 | }, | |
2403 | { | |
2404 | .path = dir_s2d3, | |
2405 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2406 | }, | |
2407 | {}, | |
2408 | }; | |
2409 | const int ruleset_fd = create_ruleset( | |
2410 | _metadata, | |
2411 | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); | |
2412 | ||
2413 | ASSERT_LE(0, ruleset_fd); | |
2414 | enforce_ruleset(_metadata, ruleset_fd); | |
2415 | ASSERT_EQ(0, close(ruleset_fd)); | |
2416 | ||
2417 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
2418 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2419 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2420 | ||
2421 | /* Denies linking because of missing MAKE_REG. */ | |
2422 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | |
2423 | ASSERT_EQ(EACCES, errno); | |
2424 | /* Denies linking because of missing source and destination REFER. */ | |
2425 | ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); | |
2426 | ASSERT_EQ(EXDEV, errno); | |
2427 | /* Denies linking because of missing source REFER. */ | |
2428 | ASSERT_EQ(-1, link(file1_s2d1, file1_s1d3)); | |
2429 | ASSERT_EQ(EXDEV, errno); | |
2430 | ||
2431 | /* Denies linking because of missing MAKE_REG. */ | |
2432 | ASSERT_EQ(-1, link(file1_s2d2, file1_s1d1)); | |
2433 | ASSERT_EQ(EACCES, errno); | |
2434 | /* Denies linking because of missing destination REFER. */ | |
2435 | ASSERT_EQ(-1, link(file1_s2d2, file1_s1d2)); | |
2436 | ASSERT_EQ(EXDEV, errno); | |
2437 | ||
2438 | /* Allows linking because of REFER and MAKE_REG. */ | |
2439 | ASSERT_EQ(0, link(file1_s2d2, file1_s1d3)); | |
2440 | ASSERT_EQ(0, unlink(file1_s2d2)); | |
2441 | /* Reverse linking denied because of missing MAKE_REG. */ | |
2442 | ASSERT_EQ(-1, link(file1_s1d3, file1_s2d2)); | |
2443 | ASSERT_EQ(EACCES, errno); | |
2444 | ASSERT_EQ(0, unlink(file1_s2d3)); | |
2445 | /* Checks reverse linking. */ | |
2446 | ASSERT_EQ(0, link(file1_s1d3, file1_s2d3)); | |
2447 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2448 | ||
2449 | /* | |
2450 | * This is OK for a file link, but it should not be allowed for a | |
2451 | * directory rename (because of the superset of access rights. | |
2452 | */ | |
2453 | ASSERT_EQ(0, link(file1_s2d3, file1_s1d3)); | |
2454 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2455 | ||
2456 | ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); | |
2457 | ASSERT_EQ(EXDEV, errno); | |
2458 | ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2)); | |
2459 | ASSERT_EQ(EXDEV, errno); | |
2460 | ||
2461 | ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); | |
2462 | ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); | |
2463 | } | |
2464 | ||
2465 | TEST_F_FORK(layout1, reparent_rename) | |
2466 | { | |
2467 | /* Same rules as for reparent_link. */ | |
2468 | const struct rule layer1[] = { | |
2469 | { | |
2470 | .path = dir_s1d2, | |
2471 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2472 | }, | |
2473 | { | |
2474 | .path = dir_s1d3, | |
2475 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2476 | }, | |
2477 | { | |
2478 | .path = dir_s2d2, | |
2479 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2480 | }, | |
2481 | { | |
2482 | .path = dir_s2d3, | |
2483 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2484 | }, | |
2485 | {}, | |
2486 | }; | |
2487 | const int ruleset_fd = create_ruleset( | |
2488 | _metadata, | |
2489 | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); | |
2490 | ||
2491 | ASSERT_LE(0, ruleset_fd); | |
2492 | enforce_ruleset(_metadata, ruleset_fd); | |
2493 | ASSERT_EQ(0, close(ruleset_fd)); | |
2494 | ||
2495 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2496 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2497 | ||
2498 | /* Denies renaming because of missing MAKE_REG. */ | |
2499 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s1d1, | |
2500 | RENAME_EXCHANGE)); | |
2501 | ASSERT_EQ(EACCES, errno); | |
2502 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1, | |
2503 | RENAME_EXCHANGE)); | |
2504 | ASSERT_EQ(EACCES, errno); | |
2505 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
2506 | ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); | |
2507 | ASSERT_EQ(EACCES, errno); | |
2508 | /* Even denies same file exchange. */ | |
2509 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file2_s1d1, | |
2510 | RENAME_EXCHANGE)); | |
2511 | ASSERT_EQ(EACCES, errno); | |
2512 | ||
2513 | /* Denies renaming because of missing source and destination REFER. */ | |
2514 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d2)); | |
2515 | ASSERT_EQ(EXDEV, errno); | |
2516 | /* | |
2517 | * Denies renaming because of missing MAKE_REG, source and destination | |
2518 | * REFER. | |
2519 | */ | |
2520 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d1, | |
2521 | RENAME_EXCHANGE)); | |
2522 | ASSERT_EQ(EACCES, errno); | |
2523 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s2d1, | |
2524 | RENAME_EXCHANGE)); | |
2525 | ASSERT_EQ(EACCES, errno); | |
2526 | ||
2527 | /* Denies renaming because of missing source REFER. */ | |
2528 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); | |
2529 | ASSERT_EQ(EXDEV, errno); | |
2530 | /* Denies renaming because of missing MAKE_REG. */ | |
2531 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d3, | |
2532 | RENAME_EXCHANGE)); | |
2533 | ASSERT_EQ(EACCES, errno); | |
2534 | ||
2535 | /* Denies renaming because of missing MAKE_REG. */ | |
2536 | ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d1)); | |
2537 | ASSERT_EQ(EACCES, errno); | |
2538 | /* Denies renaming because of missing destination REFER*/ | |
2539 | ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); | |
2540 | ASSERT_EQ(EXDEV, errno); | |
2541 | ||
2542 | /* Denies exchange because of one missing MAKE_REG. */ | |
2543 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, file2_s1d3, | |
2544 | RENAME_EXCHANGE)); | |
2545 | ASSERT_EQ(EACCES, errno); | |
2546 | /* Allows renaming because of REFER and MAKE_REG. */ | |
2547 | ASSERT_EQ(0, rename(file1_s2d2, file1_s1d3)); | |
2548 | ||
2549 | /* Reverse renaming denied because of missing MAKE_REG. */ | |
2550 | ASSERT_EQ(-1, rename(file1_s1d3, file1_s2d2)); | |
2551 | ASSERT_EQ(EACCES, errno); | |
2552 | ASSERT_EQ(0, unlink(file1_s2d3)); | |
2553 | ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); | |
2554 | ||
2555 | /* Tests reverse renaming. */ | |
2556 | ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3)); | |
2557 | ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s1d3, | |
2558 | RENAME_EXCHANGE)); | |
2559 | ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); | |
2560 | ||
2561 | /* | |
2562 | * This is OK for a file rename, but it should not be allowed for a | |
2563 | * directory rename (because of the superset of access rights). | |
2564 | */ | |
2565 | ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3)); | |
2566 | ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); | |
2567 | ||
2568 | /* | |
2569 | * Tests superset restrictions applied to directories. Not only the | |
2570 | * dir_s2d3's parent (dir_s2d2) should be taken into account but also | |
2571 | * access rights tied to dir_s2d3. dir_s2d2 is missing one access right | |
2572 | * compared to dir_s1d3/file1_s1d3 (MAKE_REG) but it is provided | |
2573 | * directly by the moved dir_s2d3. | |
2574 | */ | |
2575 | ASSERT_EQ(0, rename(dir_s2d3, file1_s1d3)); | |
2576 | ASSERT_EQ(0, rename(file1_s1d3, dir_s2d3)); | |
2577 | /* | |
2578 | * The first rename is allowed but not the exchange because dir_s1d3's | |
2579 | * parent (dir_s1d2) doesn't have REFER. | |
2580 | */ | |
2581 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3, | |
2582 | RENAME_EXCHANGE)); | |
2583 | ASSERT_EQ(EXDEV, errno); | |
2584 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s2d3, | |
2585 | RENAME_EXCHANGE)); | |
2586 | ASSERT_EQ(EXDEV, errno); | |
2587 | ASSERT_EQ(-1, rename(file1_s2d3, dir_s1d3)); | |
2588 | ASSERT_EQ(EXDEV, errno); | |
2589 | ||
2590 | ASSERT_EQ(-1, rename(file2_s1d2, file1_s1d3)); | |
2591 | ASSERT_EQ(EXDEV, errno); | |
2592 | ASSERT_EQ(-1, rename(file2_s1d3, file1_s1d2)); | |
2593 | ASSERT_EQ(EXDEV, errno); | |
2594 | ||
2595 | /* Renaming in the same directory is always allowed. */ | |
2596 | ASSERT_EQ(0, rename(file2_s1d2, file1_s1d2)); | |
2597 | ASSERT_EQ(0, rename(file2_s1d3, file1_s1d3)); | |
2598 | ||
2599 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2600 | /* Denies because of missing source MAKE_REG and destination REFER. */ | |
2601 | ASSERT_EQ(-1, rename(dir_s2d3, file1_s1d2)); | |
2602 | ASSERT_EQ(EXDEV, errno); | |
2603 | ||
2604 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2605 | /* Denies because of missing source MAKE_REG and REFER. */ | |
2606 | ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d3)); | |
2607 | ASSERT_EQ(EXDEV, errno); | |
2608 | } | |
2609 | ||
2610 | static void | |
2611 | reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata) | |
2612 | { | |
2613 | const struct rule layer1[] = { | |
2614 | { | |
2615 | .path = dir_s1d2, | |
2616 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2617 | }, | |
2618 | { | |
2619 | /* Interesting for the layer2 tests. */ | |
2620 | .path = dir_s1d3, | |
2621 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2622 | }, | |
2623 | { | |
2624 | .path = dir_s2d2, | |
2625 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2626 | }, | |
2627 | { | |
2628 | .path = dir_s2d3, | |
2629 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | |
2630 | }, | |
2631 | {}, | |
2632 | }; | |
2633 | const int ruleset_fd = create_ruleset( | |
2634 | _metadata, | |
2635 | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); | |
2636 | ||
2637 | ASSERT_LE(0, ruleset_fd); | |
2638 | enforce_ruleset(_metadata, ruleset_fd); | |
2639 | ASSERT_EQ(0, close(ruleset_fd)); | |
2640 | } | |
2641 | ||
2642 | static void | |
2643 | reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata) | |
2644 | { | |
2645 | const struct rule layer2[] = { | |
2646 | { | |
2647 | .path = dir_s2d3, | |
2648 | .access = LANDLOCK_ACCESS_FS_MAKE_DIR, | |
2649 | }, | |
2650 | {}, | |
2651 | }; | |
2652 | /* | |
2653 | * Same checks as before but with a second layer and a new MAKE_DIR | |
2654 | * rule (and no explicit handling of REFER). | |
2655 | */ | |
2656 | const int ruleset_fd = | |
2657 | create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2); | |
2658 | ||
2659 | ASSERT_LE(0, ruleset_fd); | |
2660 | enforce_ruleset(_metadata, ruleset_fd); | |
2661 | ASSERT_EQ(0, close(ruleset_fd)); | |
2662 | } | |
2663 | ||
2664 | TEST_F_FORK(layout1, reparent_exdev_layers_rename1) | |
2665 | { | |
2666 | ASSERT_EQ(0, unlink(file1_s2d2)); | |
2667 | ASSERT_EQ(0, unlink(file1_s2d3)); | |
2668 | ||
2669 | reparent_exdev_layers_enforce1(_metadata); | |
2670 | ||
2671 | /* | |
2672 | * Moving the dir_s1d3 directory below dir_s2d2 is allowed by Landlock | |
2673 | * because it doesn't inherit new access rights. | |
2674 | */ | |
2675 | ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2)); | |
2676 | ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3)); | |
2677 | ||
2678 | /* | |
2679 | * Moving the dir_s1d3 directory below dir_s2d3 is allowed, even if it | |
2680 | * gets a new inherited access rights (MAKE_REG), because MAKE_REG is | |
2681 | * already allowed for dir_s1d3. | |
2682 | */ | |
2683 | ASSERT_EQ(0, rename(dir_s1d3, file1_s2d3)); | |
2684 | ASSERT_EQ(0, rename(file1_s2d3, dir_s1d3)); | |
2685 | ||
2686 | /* | |
2687 | * However, moving the file1_s1d3 file below dir_s2d3 is allowed | |
2688 | * because it cannot inherit MAKE_REG right (which is dedicated to | |
2689 | * directories). | |
2690 | */ | |
2691 | ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3)); | |
2692 | ||
2693 | reparent_exdev_layers_enforce2(_metadata); | |
2694 | ||
2695 | /* | |
2696 | * Moving the dir_s1d3 directory below dir_s2d2 is now denied because | |
2697 | * MAKE_DIR is not tied to dir_s2d2. | |
2698 | */ | |
2699 | ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d2)); | |
2700 | ASSERT_EQ(EACCES, errno); | |
2701 | ||
2702 | /* | |
2703 | * Moving the dir_s1d3 directory below dir_s2d3 is forbidden because it | |
2704 | * would grants MAKE_REG and MAKE_DIR rights to it. | |
2705 | */ | |
2706 | ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3)); | |
2707 | ASSERT_EQ(EXDEV, errno); | |
2708 | ||
2709 | /* | |
55e55920 MS |
2710 | * Moving the file2_s1d3 file below dir_s2d3 is denied because the |
2711 | * second layer does not handle REFER, which is always denied by | |
2712 | * default. | |
f4056b92 | 2713 | */ |
55e55920 MS |
2714 | ASSERT_EQ(-1, rename(file2_s1d3, file1_s2d3)); |
2715 | ASSERT_EQ(EXDEV, errno); | |
f4056b92 MS |
2716 | } |
2717 | ||
2718 | TEST_F_FORK(layout1, reparent_exdev_layers_rename2) | |
2719 | { | |
2720 | reparent_exdev_layers_enforce1(_metadata); | |
2721 | ||
2722 | /* Checks EACCES predominance over EXDEV. */ | |
2723 | ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2)); | |
2724 | ASSERT_EQ(EACCES, errno); | |
2725 | ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d2)); | |
2726 | ASSERT_EQ(EACCES, errno); | |
2727 | ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3)); | |
2728 | ASSERT_EQ(EXDEV, errno); | |
2729 | /* Modify layout! */ | |
2730 | ASSERT_EQ(0, rename(file1_s1d2, file1_s2d3)); | |
2731 | ||
2732 | /* Without REFER source. */ | |
2733 | ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2)); | |
2734 | ASSERT_EQ(EXDEV, errno); | |
2735 | ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2)); | |
2736 | ASSERT_EQ(EXDEV, errno); | |
2737 | ||
2738 | reparent_exdev_layers_enforce2(_metadata); | |
2739 | ||
2740 | /* Checks EACCES predominance over EXDEV. */ | |
2741 | ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2)); | |
2742 | ASSERT_EQ(EACCES, errno); | |
2743 | /* Checks with actual file2_s1d2. */ | |
2744 | ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d2)); | |
2745 | ASSERT_EQ(EACCES, errno); | |
2746 | ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3)); | |
2747 | ASSERT_EQ(EXDEV, errno); | |
55e55920 MS |
2748 | /* |
2749 | * Modifying the layout is now denied because the second layer does not | |
2750 | * handle REFER, which is always denied by default. | |
2751 | */ | |
2752 | ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3)); | |
2753 | ASSERT_EQ(EXDEV, errno); | |
f4056b92 MS |
2754 | |
2755 | /* Without REFER source, EACCES wins over EXDEV. */ | |
2756 | ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2)); | |
2757 | ASSERT_EQ(EACCES, errno); | |
2758 | ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2)); | |
2759 | ASSERT_EQ(EACCES, errno); | |
2760 | } | |
2761 | ||
2762 | TEST_F_FORK(layout1, reparent_exdev_layers_exchange1) | |
2763 | { | |
2764 | const char *const dir_file1_s1d2 = file1_s1d2, *const dir_file2_s2d3 = | |
2765 | file2_s2d3; | |
2766 | ||
2767 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
2768 | ASSERT_EQ(0, mkdir(file1_s1d2, 0700)); | |
2769 | ASSERT_EQ(0, unlink(file2_s2d3)); | |
2770 | ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); | |
2771 | ||
2772 | reparent_exdev_layers_enforce1(_metadata); | |
2773 | ||
2774 | /* Error predominance with file exchange: returns EXDEV and EACCES. */ | |
2775 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3, | |
2776 | RENAME_EXCHANGE)); | |
2777 | ASSERT_EQ(EACCES, errno); | |
2778 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1, | |
2779 | RENAME_EXCHANGE)); | |
2780 | ASSERT_EQ(EACCES, errno); | |
2781 | ||
2782 | /* | |
2783 | * Checks with directories which creation could be allowed, but denied | |
2784 | * because of access rights that would be inherited. | |
2785 | */ | |
2786 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, | |
2787 | dir_file2_s2d3, RENAME_EXCHANGE)); | |
2788 | ASSERT_EQ(EXDEV, errno); | |
2789 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, | |
2790 | dir_file1_s1d2, RENAME_EXCHANGE)); | |
2791 | ASSERT_EQ(EXDEV, errno); | |
2792 | ||
2793 | /* Checks with same access rights. */ | |
2794 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3, | |
2795 | RENAME_EXCHANGE)); | |
2796 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, | |
2797 | RENAME_EXCHANGE)); | |
2798 | ||
2799 | /* Checks with different (child-only) access rights. */ | |
2800 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2, | |
2801 | RENAME_EXCHANGE)); | |
2802 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3, | |
2803 | RENAME_EXCHANGE)); | |
2804 | ||
2805 | /* | |
2806 | * Checks that exchange between file and directory are consistent. | |
2807 | * | |
2808 | * Moving a file (file1_s2d2) to a directory which only grants more | |
2809 | * directory-related access rights is allowed, and at the same time | |
2810 | * moving a directory (dir_file2_s2d3) to another directory which | |
2811 | * grants less access rights is allowed too. | |
2812 | * | |
2813 | * See layout1.reparent_exdev_layers_exchange3 for inverted arguments. | |
2814 | */ | |
2815 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, | |
2816 | RENAME_EXCHANGE)); | |
2817 | /* | |
2818 | * However, moving back the directory is denied because it would get | |
2819 | * more access rights than the current state and because file creation | |
2820 | * is forbidden (in dir_s2d2). | |
2821 | */ | |
2822 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, | |
2823 | RENAME_EXCHANGE)); | |
2824 | ASSERT_EQ(EACCES, errno); | |
2825 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, | |
2826 | RENAME_EXCHANGE)); | |
2827 | ASSERT_EQ(EACCES, errno); | |
2828 | ||
2829 | reparent_exdev_layers_enforce2(_metadata); | |
2830 | ||
2831 | /* Error predominance with file exchange: returns EXDEV and EACCES. */ | |
2832 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3, | |
2833 | RENAME_EXCHANGE)); | |
2834 | ASSERT_EQ(EACCES, errno); | |
2835 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1, | |
2836 | RENAME_EXCHANGE)); | |
2837 | ASSERT_EQ(EACCES, errno); | |
2838 | ||
2839 | /* Checks with directories which creation is now denied. */ | |
2840 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, | |
2841 | dir_file2_s2d3, RENAME_EXCHANGE)); | |
2842 | ASSERT_EQ(EACCES, errno); | |
2843 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, | |
2844 | dir_file1_s1d2, RENAME_EXCHANGE)); | |
2845 | ASSERT_EQ(EACCES, errno); | |
2846 | ||
2847 | /* Checks with different (child-only) access rights. */ | |
2848 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3, | |
2849 | RENAME_EXCHANGE)); | |
2850 | /* Denied because of MAKE_DIR. */ | |
2851 | ASSERT_EQ(EACCES, errno); | |
2852 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, | |
2853 | RENAME_EXCHANGE)); | |
2854 | ASSERT_EQ(EACCES, errno); | |
2855 | ||
2856 | /* Checks with different (child-only) access rights. */ | |
2857 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2, | |
2858 | RENAME_EXCHANGE)); | |
2859 | /* Denied because of MAKE_DIR. */ | |
2860 | ASSERT_EQ(EACCES, errno); | |
2861 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3, | |
2862 | RENAME_EXCHANGE)); | |
2863 | ASSERT_EQ(EACCES, errno); | |
2864 | ||
2865 | /* See layout1.reparent_exdev_layers_exchange2 for complement. */ | |
2866 | } | |
2867 | ||
2868 | TEST_F_FORK(layout1, reparent_exdev_layers_exchange2) | |
2869 | { | |
2870 | const char *const dir_file2_s2d3 = file2_s2d3; | |
2871 | ||
2872 | ASSERT_EQ(0, unlink(file2_s2d3)); | |
2873 | ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); | |
2874 | ||
2875 | reparent_exdev_layers_enforce1(_metadata); | |
2876 | reparent_exdev_layers_enforce2(_metadata); | |
2877 | ||
2878 | /* Checks that exchange between file and directory are consistent. */ | |
2879 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, | |
2880 | RENAME_EXCHANGE)); | |
2881 | ASSERT_EQ(EACCES, errno); | |
2882 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, | |
2883 | RENAME_EXCHANGE)); | |
2884 | ASSERT_EQ(EACCES, errno); | |
2885 | } | |
2886 | ||
2887 | TEST_F_FORK(layout1, reparent_exdev_layers_exchange3) | |
2888 | { | |
2889 | const char *const dir_file2_s2d3 = file2_s2d3; | |
2890 | ||
2891 | ASSERT_EQ(0, unlink(file2_s2d3)); | |
2892 | ASSERT_EQ(0, mkdir(file2_s2d3, 0700)); | |
2893 | ||
2894 | reparent_exdev_layers_enforce1(_metadata); | |
2895 | ||
2896 | /* | |
2897 | * Checks that exchange between file and directory are consistent, | |
2898 | * including with inverted arguments (see | |
2899 | * layout1.reparent_exdev_layers_exchange1). | |
2900 | */ | |
2901 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, | |
2902 | RENAME_EXCHANGE)); | |
2903 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3, | |
2904 | RENAME_EXCHANGE)); | |
2905 | ASSERT_EQ(EACCES, errno); | |
2906 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2, | |
2907 | RENAME_EXCHANGE)); | |
2908 | ASSERT_EQ(EACCES, errno); | |
2909 | } | |
2910 | ||
2911 | TEST_F_FORK(layout1, reparent_remove) | |
2912 | { | |
2913 | const struct rule layer1[] = { | |
2914 | { | |
2915 | .path = dir_s1d1, | |
2916 | .access = LANDLOCK_ACCESS_FS_REFER | | |
2917 | LANDLOCK_ACCESS_FS_REMOVE_DIR, | |
2918 | }, | |
2919 | { | |
2920 | .path = dir_s1d2, | |
2921 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2922 | }, | |
2923 | { | |
2924 | .path = dir_s2d1, | |
2925 | .access = LANDLOCK_ACCESS_FS_REFER | | |
2926 | LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2927 | }, | |
2928 | {}, | |
2929 | }; | |
2930 | const int ruleset_fd = create_ruleset( | |
2931 | _metadata, | |
2932 | LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR | | |
2933 | LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
2934 | layer1); | |
2935 | ||
2936 | ASSERT_LE(0, ruleset_fd); | |
2937 | enforce_ruleset(_metadata, ruleset_fd); | |
2938 | ASSERT_EQ(0, close(ruleset_fd)); | |
2939 | ||
2940 | /* Access denied because of wrong/swapped remove file/dir. */ | |
2941 | ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2)); | |
2942 | ASSERT_EQ(EACCES, errno); | |
2943 | ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d1)); | |
2944 | ASSERT_EQ(EACCES, errno); | |
2945 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d2, | |
2946 | RENAME_EXCHANGE)); | |
2947 | ASSERT_EQ(EACCES, errno); | |
2948 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d3, | |
2949 | RENAME_EXCHANGE)); | |
2950 | ASSERT_EQ(EACCES, errno); | |
2951 | ||
2952 | /* Access allowed thanks to the matching rights. */ | |
2953 | ASSERT_EQ(-1, rename(file1_s2d1, dir_s1d2)); | |
2954 | ASSERT_EQ(EISDIR, errno); | |
2955 | ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d1)); | |
2956 | ASSERT_EQ(ENOTDIR, errno); | |
2957 | ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1)); | |
2958 | ASSERT_EQ(ENOTDIR, errno); | |
2959 | ASSERT_EQ(0, unlink(file1_s2d1)); | |
2960 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
2961 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
2962 | ASSERT_EQ(0, rename(dir_s1d3, file1_s2d1)); | |
2963 | ||
2964 | /* Effectively removes a file and a directory by exchanging them. */ | |
2965 | ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); | |
2966 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, | |
2967 | RENAME_EXCHANGE)); | |
2968 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, | |
2969 | RENAME_EXCHANGE)); | |
2970 | ASSERT_EQ(EACCES, errno); | |
2971 | } | |
2972 | ||
2973 | TEST_F_FORK(layout1, reparent_dom_superset) | |
2974 | { | |
2975 | const struct rule layer1[] = { | |
2976 | { | |
2977 | .path = dir_s1d2, | |
2978 | .access = LANDLOCK_ACCESS_FS_REFER, | |
2979 | }, | |
2980 | { | |
2981 | .path = file1_s1d2, | |
2982 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
2983 | }, | |
2984 | { | |
2985 | .path = dir_s1d3, | |
2986 | .access = LANDLOCK_ACCESS_FS_MAKE_SOCK | | |
2987 | LANDLOCK_ACCESS_FS_EXECUTE, | |
2988 | }, | |
2989 | { | |
2990 | .path = dir_s2d2, | |
2991 | .access = LANDLOCK_ACCESS_FS_REFER | | |
2992 | LANDLOCK_ACCESS_FS_EXECUTE | | |
2993 | LANDLOCK_ACCESS_FS_MAKE_SOCK, | |
2994 | }, | |
2995 | { | |
2996 | .path = dir_s2d3, | |
2997 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
2998 | LANDLOCK_ACCESS_FS_MAKE_FIFO, | |
2999 | }, | |
3000 | {}, | |
3001 | }; | |
3002 | int ruleset_fd = create_ruleset(_metadata, | |
3003 | LANDLOCK_ACCESS_FS_REFER | | |
3004 | LANDLOCK_ACCESS_FS_EXECUTE | | |
3005 | LANDLOCK_ACCESS_FS_MAKE_SOCK | | |
3006 | LANDLOCK_ACCESS_FS_READ_FILE | | |
3007 | LANDLOCK_ACCESS_FS_MAKE_FIFO, | |
3008 | layer1); | |
3009 | ||
3010 | ASSERT_LE(0, ruleset_fd); | |
3011 | enforce_ruleset(_metadata, ruleset_fd); | |
3012 | ASSERT_EQ(0, close(ruleset_fd)); | |
3013 | ||
3014 | ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1)); | |
3015 | ASSERT_EQ(EXDEV, errno); | |
3016 | /* | |
3017 | * Moving file1_s1d2 beneath dir_s2d3 would grant it the READ_FILE | |
3018 | * access right. | |
3019 | */ | |
3020 | ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d3)); | |
3021 | ASSERT_EQ(EXDEV, errno); | |
3022 | /* | |
3023 | * Moving file1_s1d2 should be allowed even if dir_s2d2 grants a | |
3024 | * superset of access rights compared to dir_s1d2, because file1_s1d2 | |
3025 | * already has these access rights anyway. | |
3026 | */ | |
3027 | ASSERT_EQ(0, rename(file1_s1d2, file1_s2d2)); | |
3028 | ASSERT_EQ(0, rename(file1_s2d2, file1_s1d2)); | |
3029 | ||
3030 | ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1)); | |
3031 | ASSERT_EQ(EXDEV, errno); | |
3032 | /* | |
3033 | * Moving dir_s1d3 beneath dir_s2d3 would grant it the MAKE_FIFO access | |
3034 | * right. | |
3035 | */ | |
3036 | ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3)); | |
3037 | ASSERT_EQ(EXDEV, errno); | |
3038 | /* | |
3039 | * Moving dir_s1d3 should be allowed even if dir_s2d2 grants a superset | |
3040 | * of access rights compared to dir_s1d2, because dir_s1d3 already has | |
3041 | * these access rights anyway. | |
3042 | */ | |
3043 | ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2)); | |
3044 | ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3)); | |
3045 | ||
3046 | /* | |
3047 | * Moving file1_s2d3 beneath dir_s1d2 is allowed, but moving it back | |
3048 | * will be denied because the new inherited access rights from dir_s1d2 | |
3049 | * will be less than the destination (original) dir_s2d3. This is a | |
3050 | * sinkhole scenario where we cannot move back files or directories. | |
3051 | */ | |
3052 | ASSERT_EQ(0, rename(file1_s2d3, file2_s1d2)); | |
3053 | ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3)); | |
3054 | ASSERT_EQ(EXDEV, errno); | |
3055 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
3056 | ASSERT_EQ(0, unlink(file2_s2d3)); | |
3057 | /* | |
3058 | * Checks similar directory one-way move: dir_s2d3 loses EXECUTE and | |
3059 | * MAKE_SOCK which were inherited from dir_s1d3. | |
3060 | */ | |
3061 | ASSERT_EQ(0, rename(dir_s2d3, file2_s1d2)); | |
3062 | ASSERT_EQ(-1, rename(file2_s1d2, dir_s2d3)); | |
3063 | ASSERT_EQ(EXDEV, errno); | |
3064 | } | |
3065 | ||
e1199815 MS |
3066 | TEST_F_FORK(layout1, remove_dir) |
3067 | { | |
3068 | const struct rule rules[] = { | |
3069 | { | |
3070 | .path = dir_s1d2, | |
3071 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | |
3072 | }, | |
135464f9 | 3073 | {}, |
e1199815 | 3074 | }; |
371183fa MS |
3075 | const int ruleset_fd = |
3076 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
3077 | |
3078 | ASSERT_LE(0, ruleset_fd); | |
3079 | ||
3080 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
3081 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3082 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
3083 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
3084 | ||
3085 | enforce_ruleset(_metadata, ruleset_fd); | |
3086 | ASSERT_EQ(0, close(ruleset_fd)); | |
3087 | ||
3088 | ASSERT_EQ(0, rmdir(dir_s1d3)); | |
3089 | ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); | |
3090 | ASSERT_EQ(0, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR)); | |
3091 | ||
3092 | /* dir_s1d2 itself cannot be removed. */ | |
3093 | ASSERT_EQ(-1, rmdir(dir_s1d2)); | |
3094 | ASSERT_EQ(EACCES, errno); | |
3095 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d2, AT_REMOVEDIR)); | |
3096 | ASSERT_EQ(EACCES, errno); | |
3097 | ASSERT_EQ(-1, rmdir(dir_s1d1)); | |
3098 | ASSERT_EQ(EACCES, errno); | |
3099 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d1, AT_REMOVEDIR)); | |
3100 | ASSERT_EQ(EACCES, errno); | |
3101 | } | |
3102 | ||
3103 | TEST_F_FORK(layout1, remove_file) | |
3104 | { | |
3105 | const struct rule rules[] = { | |
3106 | { | |
3107 | .path = dir_s1d2, | |
3108 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | |
3109 | }, | |
135464f9 | 3110 | {}, |
e1199815 | 3111 | }; |
371183fa MS |
3112 | const int ruleset_fd = |
3113 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
3114 | |
3115 | ASSERT_LE(0, ruleset_fd); | |
3116 | enforce_ruleset(_metadata, ruleset_fd); | |
3117 | ASSERT_EQ(0, close(ruleset_fd)); | |
3118 | ||
3119 | ASSERT_EQ(-1, unlink(file1_s1d1)); | |
3120 | ASSERT_EQ(EACCES, errno); | |
3121 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, file1_s1d1, 0)); | |
3122 | ASSERT_EQ(EACCES, errno); | |
3123 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3124 | ASSERT_EQ(0, unlinkat(AT_FDCWD, file1_s1d3, 0)); | |
3125 | } | |
3126 | ||
3127 | static void test_make_file(struct __test_metadata *const _metadata, | |
371183fa MS |
3128 | const __u64 access, const mode_t mode, |
3129 | const dev_t dev) | |
e1199815 MS |
3130 | { |
3131 | const struct rule rules[] = { | |
3132 | { | |
3133 | .path = dir_s1d2, | |
3134 | .access = access, | |
3135 | }, | |
135464f9 | 3136 | {}, |
e1199815 MS |
3137 | }; |
3138 | const int ruleset_fd = create_ruleset(_metadata, access, rules); | |
3139 | ||
3140 | ASSERT_LE(0, ruleset_fd); | |
3141 | ||
3142 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
3143 | ASSERT_EQ(0, unlink(file2_s1d1)); | |
371183fa MS |
3144 | ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev)) |
3145 | { | |
3146 | TH_LOG("Failed to make file \"%s\": %s", file2_s1d1, | |
3147 | strerror(errno)); | |
e1199815 MS |
3148 | }; |
3149 | ||
3150 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3151 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
3152 | ||
3153 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
3154 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
3155 | ||
3156 | enforce_ruleset(_metadata, ruleset_fd); | |
3157 | ASSERT_EQ(0, close(ruleset_fd)); | |
3158 | ||
3159 | ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev)); | |
3160 | ASSERT_EQ(EACCES, errno); | |
3161 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | |
3162 | ASSERT_EQ(EACCES, errno); | |
3163 | ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); | |
3164 | ASSERT_EQ(EACCES, errno); | |
3165 | ||
371183fa MS |
3166 | ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev)) |
3167 | { | |
3168 | TH_LOG("Failed to make file \"%s\": %s", file1_s1d2, | |
3169 | strerror(errno)); | |
e1199815 MS |
3170 | }; |
3171 | ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); | |
3172 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
3173 | ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); | |
3174 | ||
3175 | ASSERT_EQ(0, mknod(file1_s1d3, mode | 0400, dev)); | |
3176 | ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); | |
3177 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
3178 | ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); | |
3179 | } | |
3180 | ||
3181 | TEST_F_FORK(layout1, make_char) | |
3182 | { | |
3183 | /* Creates a /dev/null device. */ | |
3184 | set_cap(_metadata, CAP_MKNOD); | |
3185 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR, | |
371183fa | 3186 | makedev(1, 3)); |
e1199815 MS |
3187 | } |
3188 | ||
3189 | TEST_F_FORK(layout1, make_block) | |
3190 | { | |
3191 | /* Creates a /dev/loop0 device. */ | |
3192 | set_cap(_metadata, CAP_MKNOD); | |
3193 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK, | |
371183fa | 3194 | makedev(7, 0)); |
e1199815 MS |
3195 | } |
3196 | ||
3197 | TEST_F_FORK(layout1, make_reg_1) | |
3198 | { | |
3199 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFREG, 0); | |
3200 | } | |
3201 | ||
3202 | TEST_F_FORK(layout1, make_reg_2) | |
3203 | { | |
3204 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, 0, 0); | |
3205 | } | |
3206 | ||
3207 | TEST_F_FORK(layout1, make_sock) | |
3208 | { | |
3209 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_SOCK, S_IFSOCK, 0); | |
3210 | } | |
3211 | ||
3212 | TEST_F_FORK(layout1, make_fifo) | |
3213 | { | |
3214 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_FIFO, S_IFIFO, 0); | |
3215 | } | |
3216 | ||
3217 | TEST_F_FORK(layout1, make_sym) | |
3218 | { | |
3219 | const struct rule rules[] = { | |
3220 | { | |
3221 | .path = dir_s1d2, | |
3222 | .access = LANDLOCK_ACCESS_FS_MAKE_SYM, | |
3223 | }, | |
135464f9 | 3224 | {}, |
e1199815 | 3225 | }; |
371183fa MS |
3226 | const int ruleset_fd = |
3227 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
3228 | |
3229 | ASSERT_LE(0, ruleset_fd); | |
3230 | ||
3231 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
3232 | ASSERT_EQ(0, unlink(file2_s1d1)); | |
3233 | ASSERT_EQ(0, symlink("none", file2_s1d1)); | |
3234 | ||
3235 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3236 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
3237 | ||
3238 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
3239 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
3240 | ||
3241 | enforce_ruleset(_metadata, ruleset_fd); | |
3242 | ASSERT_EQ(0, close(ruleset_fd)); | |
3243 | ||
3244 | ASSERT_EQ(-1, symlink("none", file1_s1d1)); | |
3245 | ASSERT_EQ(EACCES, errno); | |
3246 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | |
3247 | ASSERT_EQ(EACCES, errno); | |
3248 | ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); | |
3249 | ASSERT_EQ(EACCES, errno); | |
3250 | ||
3251 | ASSERT_EQ(0, symlink("none", file1_s1d2)); | |
3252 | ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); | |
3253 | ASSERT_EQ(0, unlink(file2_s1d2)); | |
3254 | ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); | |
3255 | ||
3256 | ASSERT_EQ(0, symlink("none", file1_s1d3)); | |
3257 | ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); | |
3258 | ASSERT_EQ(0, unlink(file2_s1d3)); | |
3259 | ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); | |
3260 | } | |
3261 | ||
3262 | TEST_F_FORK(layout1, make_dir) | |
3263 | { | |
3264 | const struct rule rules[] = { | |
3265 | { | |
3266 | .path = dir_s1d2, | |
3267 | .access = LANDLOCK_ACCESS_FS_MAKE_DIR, | |
3268 | }, | |
135464f9 | 3269 | {}, |
e1199815 | 3270 | }; |
371183fa MS |
3271 | const int ruleset_fd = |
3272 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
3273 | |
3274 | ASSERT_LE(0, ruleset_fd); | |
3275 | ||
3276 | ASSERT_EQ(0, unlink(file1_s1d1)); | |
3277 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3278 | ASSERT_EQ(0, unlink(file1_s1d3)); | |
3279 | ||
3280 | enforce_ruleset(_metadata, ruleset_fd); | |
3281 | ASSERT_EQ(0, close(ruleset_fd)); | |
3282 | ||
3283 | /* Uses file_* as directory names. */ | |
3284 | ASSERT_EQ(-1, mkdir(file1_s1d1, 0700)); | |
3285 | ASSERT_EQ(EACCES, errno); | |
3286 | ASSERT_EQ(0, mkdir(file1_s1d2, 0700)); | |
3287 | ASSERT_EQ(0, mkdir(file1_s1d3, 0700)); | |
3288 | } | |
3289 | ||
3290 | static int open_proc_fd(struct __test_metadata *const _metadata, const int fd, | |
371183fa | 3291 | const int open_flags) |
e1199815 MS |
3292 | { |
3293 | static const char path_template[] = "/proc/self/fd/%d"; | |
3294 | char procfd_path[sizeof(path_template) + 10]; | |
371183fa MS |
3295 | const int procfd_path_size = |
3296 | snprintf(procfd_path, sizeof(procfd_path), path_template, fd); | |
e1199815 MS |
3297 | |
3298 | ASSERT_LT(procfd_path_size, sizeof(procfd_path)); | |
3299 | return open(procfd_path, open_flags); | |
3300 | } | |
3301 | ||
3302 | TEST_F_FORK(layout1, proc_unlinked_file) | |
3303 | { | |
3304 | const struct rule rules[] = { | |
3305 | { | |
3306 | .path = file1_s1d2, | |
3307 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
3308 | }, | |
135464f9 | 3309 | {}, |
e1199815 MS |
3310 | }; |
3311 | int reg_fd, proc_fd; | |
371183fa MS |
3312 | const int ruleset_fd = create_ruleset( |
3313 | _metadata, | |
3314 | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3315 | rules); | |
e1199815 MS |
3316 | |
3317 | ASSERT_LE(0, ruleset_fd); | |
3318 | enforce_ruleset(_metadata, ruleset_fd); | |
3319 | ASSERT_EQ(0, close(ruleset_fd)); | |
3320 | ||
3321 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); | |
3322 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
3323 | reg_fd = open(file1_s1d2, O_RDONLY | O_CLOEXEC); | |
3324 | ASSERT_LE(0, reg_fd); | |
3325 | ASSERT_EQ(0, unlink(file1_s1d2)); | |
3326 | ||
3327 | proc_fd = open_proc_fd(_metadata, reg_fd, O_RDONLY | O_CLOEXEC); | |
3328 | ASSERT_LE(0, proc_fd); | |
3329 | ASSERT_EQ(0, close(proc_fd)); | |
3330 | ||
3331 | proc_fd = open_proc_fd(_metadata, reg_fd, O_RDWR | O_CLOEXEC); | |
371183fa MS |
3332 | ASSERT_EQ(-1, proc_fd) |
3333 | { | |
3334 | TH_LOG("Successfully opened /proc/self/fd/%d: %s", reg_fd, | |
3335 | strerror(errno)); | |
e1199815 MS |
3336 | } |
3337 | ASSERT_EQ(EACCES, errno); | |
3338 | ||
3339 | ASSERT_EQ(0, close(reg_fd)); | |
3340 | } | |
3341 | ||
3342 | TEST_F_FORK(layout1, proc_pipe) | |
3343 | { | |
3344 | int proc_fd; | |
3345 | int pipe_fds[2]; | |
3346 | char buf = '\0'; | |
3347 | const struct rule rules[] = { | |
3348 | { | |
3349 | .path = dir_s1d2, | |
3350 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 3351 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 | 3352 | }, |
135464f9 | 3353 | {}, |
e1199815 MS |
3354 | }; |
3355 | /* Limits read and write access to files tied to the filesystem. */ | |
371183fa MS |
3356 | const int ruleset_fd = |
3357 | create_ruleset(_metadata, rules[0].access, rules); | |
e1199815 MS |
3358 | |
3359 | ASSERT_LE(0, ruleset_fd); | |
3360 | enforce_ruleset(_metadata, ruleset_fd); | |
3361 | ASSERT_EQ(0, close(ruleset_fd)); | |
3362 | ||
3363 | /* Checks enforcement for normal files. */ | |
3364 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | |
3365 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | |
3366 | ||
3367 | /* Checks access to pipes through FD. */ | |
3368 | ASSERT_EQ(0, pipe2(pipe_fds, O_CLOEXEC)); | |
371183fa MS |
3369 | ASSERT_EQ(1, write(pipe_fds[1], ".", 1)) |
3370 | { | |
e1199815 MS |
3371 | TH_LOG("Failed to write in pipe: %s", strerror(errno)); |
3372 | } | |
3373 | ASSERT_EQ(1, read(pipe_fds[0], &buf, 1)); | |
3374 | ASSERT_EQ('.', buf); | |
3375 | ||
3376 | /* Checks write access to pipe through /proc/self/fd . */ | |
3377 | proc_fd = open_proc_fd(_metadata, pipe_fds[1], O_WRONLY | O_CLOEXEC); | |
3378 | ASSERT_LE(0, proc_fd); | |
371183fa MS |
3379 | ASSERT_EQ(1, write(proc_fd, ".", 1)) |
3380 | { | |
e1199815 | 3381 | TH_LOG("Failed to write through /proc/self/fd/%d: %s", |
371183fa | 3382 | pipe_fds[1], strerror(errno)); |
e1199815 MS |
3383 | } |
3384 | ASSERT_EQ(0, close(proc_fd)); | |
3385 | ||
3386 | /* Checks read access to pipe through /proc/self/fd . */ | |
3387 | proc_fd = open_proc_fd(_metadata, pipe_fds[0], O_RDONLY | O_CLOEXEC); | |
3388 | ASSERT_LE(0, proc_fd); | |
3389 | buf = '\0'; | |
371183fa MS |
3390 | ASSERT_EQ(1, read(proc_fd, &buf, 1)) |
3391 | { | |
e1199815 | 3392 | TH_LOG("Failed to read through /proc/self/fd/%d: %s", |
371183fa | 3393 | pipe_fds[1], strerror(errno)); |
e1199815 MS |
3394 | } |
3395 | ASSERT_EQ(0, close(proc_fd)); | |
3396 | ||
3397 | ASSERT_EQ(0, close(pipe_fds[0])); | |
3398 | ASSERT_EQ(0, close(pipe_fds[1])); | |
3399 | } | |
3400 | ||
225351ab GN |
3401 | /* Invokes truncate(2) and returns its errno or 0. */ |
3402 | static int test_truncate(const char *const path) | |
3403 | { | |
3404 | if (truncate(path, 10) < 0) | |
3405 | return errno; | |
3406 | return 0; | |
3407 | } | |
3408 | ||
3409 | /* | |
3410 | * Invokes creat(2) and returns its errno or 0. | |
3411 | * Closes the opened file descriptor on success. | |
3412 | */ | |
3413 | static int test_creat(const char *const path) | |
3414 | { | |
3415 | int fd = creat(path, 0600); | |
3416 | ||
3417 | if (fd < 0) | |
3418 | return errno; | |
3419 | ||
3420 | /* | |
3421 | * Mixing error codes from close(2) and creat(2) should not lead to any | |
3422 | * (access type) confusion for this test. | |
3423 | */ | |
3424 | if (close(fd) < 0) | |
3425 | return errno; | |
3426 | return 0; | |
3427 | } | |
3428 | ||
3429 | /* | |
3430 | * Exercises file truncation when it's not restricted, | |
3431 | * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed. | |
3432 | */ | |
3433 | TEST_F_FORK(layout1, truncate_unhandled) | |
3434 | { | |
3435 | const char *const file_r = file1_s1d1; | |
3436 | const char *const file_w = file2_s1d1; | |
3437 | const char *const file_none = file1_s1d2; | |
3438 | const struct rule rules[] = { | |
3439 | { | |
3440 | .path = file_r, | |
3441 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
3442 | }, | |
3443 | { | |
3444 | .path = file_w, | |
3445 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3446 | }, | |
3447 | /* Implicitly: No rights for file_none. */ | |
3448 | {}, | |
3449 | }; | |
3450 | ||
3451 | const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | | |
3452 | LANDLOCK_ACCESS_FS_WRITE_FILE; | |
3453 | int ruleset_fd; | |
3454 | ||
3455 | /* Enable Landlock. */ | |
3456 | ruleset_fd = create_ruleset(_metadata, handled, rules); | |
3457 | ||
3458 | ASSERT_LE(0, ruleset_fd); | |
3459 | enforce_ruleset(_metadata, ruleset_fd); | |
3460 | ASSERT_EQ(0, close(ruleset_fd)); | |
3461 | ||
3462 | /* | |
3463 | * Checks read right: truncate and open with O_TRUNC work, unless the | |
3464 | * file is attempted to be opened for writing. | |
3465 | */ | |
3466 | EXPECT_EQ(0, test_truncate(file_r)); | |
3467 | EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC)); | |
3468 | EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC)); | |
3469 | EXPECT_EQ(EACCES, test_creat(file_r)); | |
3470 | ||
3471 | /* | |
3472 | * Checks write right: truncate and open with O_TRUNC work, unless the | |
3473 | * file is attempted to be opened for reading. | |
3474 | */ | |
3475 | EXPECT_EQ(0, test_truncate(file_w)); | |
3476 | EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC)); | |
3477 | EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC)); | |
3478 | EXPECT_EQ(0, test_creat(file_w)); | |
3479 | ||
3480 | /* | |
3481 | * Checks "no rights" case: truncate works but all open attempts fail, | |
3482 | * including creat. | |
3483 | */ | |
3484 | EXPECT_EQ(0, test_truncate(file_none)); | |
3485 | EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); | |
3486 | EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); | |
3487 | EXPECT_EQ(EACCES, test_creat(file_none)); | |
3488 | } | |
3489 | ||
3490 | TEST_F_FORK(layout1, truncate) | |
3491 | { | |
3492 | const char *const file_rwt = file1_s1d1; | |
3493 | const char *const file_rw = file2_s1d1; | |
3494 | const char *const file_rt = file1_s1d2; | |
3495 | const char *const file_t = file2_s1d2; | |
3496 | const char *const file_none = file1_s1d3; | |
3497 | const char *const dir_t = dir_s2d1; | |
3498 | const char *const file_in_dir_t = file1_s2d1; | |
3499 | const char *const dir_w = dir_s3d1; | |
3500 | const char *const file_in_dir_w = file1_s3d1; | |
3501 | const struct rule rules[] = { | |
3502 | { | |
3503 | .path = file_rwt, | |
3504 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
3505 | LANDLOCK_ACCESS_FS_WRITE_FILE | | |
3506 | LANDLOCK_ACCESS_FS_TRUNCATE, | |
3507 | }, | |
3508 | { | |
3509 | .path = file_rw, | |
3510 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
3511 | LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3512 | }, | |
3513 | { | |
3514 | .path = file_rt, | |
3515 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
3516 | LANDLOCK_ACCESS_FS_TRUNCATE, | |
3517 | }, | |
3518 | { | |
3519 | .path = file_t, | |
3520 | .access = LANDLOCK_ACCESS_FS_TRUNCATE, | |
3521 | }, | |
3522 | /* Implicitly: No access rights for file_none. */ | |
3523 | { | |
3524 | .path = dir_t, | |
3525 | .access = LANDLOCK_ACCESS_FS_TRUNCATE, | |
3526 | }, | |
3527 | { | |
3528 | .path = dir_w, | |
3529 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3530 | }, | |
3531 | {}, | |
3532 | }; | |
3533 | const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | | |
3534 | LANDLOCK_ACCESS_FS_WRITE_FILE | | |
3535 | LANDLOCK_ACCESS_FS_TRUNCATE; | |
3536 | int ruleset_fd; | |
3537 | ||
3538 | /* Enable Landlock. */ | |
3539 | ruleset_fd = create_ruleset(_metadata, handled, rules); | |
3540 | ||
3541 | ASSERT_LE(0, ruleset_fd); | |
3542 | enforce_ruleset(_metadata, ruleset_fd); | |
3543 | ASSERT_EQ(0, close(ruleset_fd)); | |
3544 | ||
3545 | /* Checks read, write and truncate rights: truncation works. */ | |
3546 | EXPECT_EQ(0, test_truncate(file_rwt)); | |
3547 | EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC)); | |
3548 | EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC)); | |
3549 | ||
3550 | /* Checks read and write rights: no truncate variant works. */ | |
3551 | EXPECT_EQ(EACCES, test_truncate(file_rw)); | |
3552 | EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC)); | |
3553 | EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC)); | |
3554 | ||
3555 | /* | |
3556 | * Checks read and truncate rights: truncation works. | |
3557 | * | |
3558 | * Note: Files can get truncated using open() even with O_RDONLY. | |
3559 | */ | |
3560 | EXPECT_EQ(0, test_truncate(file_rt)); | |
3561 | EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC)); | |
3562 | EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC)); | |
3563 | ||
3564 | /* Checks truncate right: truncate works, but can't open file. */ | |
3565 | EXPECT_EQ(0, test_truncate(file_t)); | |
3566 | EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC)); | |
3567 | EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC)); | |
3568 | ||
3569 | /* Checks "no rights" case: No form of truncation works. */ | |
3570 | EXPECT_EQ(EACCES, test_truncate(file_none)); | |
3571 | EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); | |
3572 | EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); | |
3573 | ||
3574 | /* | |
3575 | * Checks truncate right on directory: truncate works on contained | |
3576 | * files. | |
3577 | */ | |
3578 | EXPECT_EQ(0, test_truncate(file_in_dir_t)); | |
3579 | EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC)); | |
3580 | EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC)); | |
3581 | ||
3582 | /* | |
3583 | * Checks creat in dir_w: This requires the truncate right when | |
3584 | * overwriting an existing file, but does not require it when the file | |
3585 | * is new. | |
3586 | */ | |
3587 | EXPECT_EQ(EACCES, test_creat(file_in_dir_w)); | |
3588 | ||
3589 | ASSERT_EQ(0, unlink(file_in_dir_w)); | |
3590 | EXPECT_EQ(0, test_creat(file_in_dir_w)); | |
3591 | } | |
3592 | ||
3593 | /* Invokes ftruncate(2) and returns its errno or 0. */ | |
3594 | static int test_ftruncate(int fd) | |
3595 | { | |
3596 | if (ftruncate(fd, 10) < 0) | |
3597 | return errno; | |
3598 | return 0; | |
3599 | } | |
3600 | ||
3601 | TEST_F_FORK(layout1, ftruncate) | |
3602 | { | |
3603 | /* | |
3604 | * This test opens a new file descriptor at different stages of | |
3605 | * Landlock restriction: | |
3606 | * | |
3607 | * without restriction: ftruncate works | |
3608 | * something else but truncate restricted: ftruncate works | |
3609 | * truncate restricted and permitted: ftruncate works | |
3610 | * truncate restricted and not permitted: ftruncate fails | |
3611 | * | |
3612 | * Whether this works or not is expected to depend on the time when the | |
3613 | * FD was opened, not to depend on the time when ftruncate() was | |
3614 | * called. | |
3615 | */ | |
3616 | const char *const path = file1_s1d1; | |
3617 | const __u64 handled1 = LANDLOCK_ACCESS_FS_READ_FILE | | |
3618 | LANDLOCK_ACCESS_FS_WRITE_FILE; | |
3619 | const struct rule layer1[] = { | |
3620 | { | |
3621 | .path = path, | |
3622 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3623 | }, | |
3624 | {}, | |
3625 | }; | |
3626 | const __u64 handled2 = LANDLOCK_ACCESS_FS_TRUNCATE; | |
3627 | const struct rule layer2[] = { | |
3628 | { | |
3629 | .path = path, | |
3630 | .access = LANDLOCK_ACCESS_FS_TRUNCATE, | |
3631 | }, | |
3632 | {}, | |
3633 | }; | |
3634 | const __u64 handled3 = LANDLOCK_ACCESS_FS_TRUNCATE | | |
3635 | LANDLOCK_ACCESS_FS_WRITE_FILE; | |
3636 | const struct rule layer3[] = { | |
3637 | { | |
3638 | .path = path, | |
3639 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3640 | }, | |
3641 | {}, | |
3642 | }; | |
3643 | int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd; | |
3644 | ||
3645 | fd_layer0 = open(path, O_WRONLY); | |
3646 | EXPECT_EQ(0, test_ftruncate(fd_layer0)); | |
3647 | ||
3648 | ruleset_fd = create_ruleset(_metadata, handled1, layer1); | |
3649 | ASSERT_LE(0, ruleset_fd); | |
3650 | enforce_ruleset(_metadata, ruleset_fd); | |
3651 | ASSERT_EQ(0, close(ruleset_fd)); | |
3652 | ||
3653 | fd_layer1 = open(path, O_WRONLY); | |
3654 | EXPECT_EQ(0, test_ftruncate(fd_layer0)); | |
3655 | EXPECT_EQ(0, test_ftruncate(fd_layer1)); | |
3656 | ||
3657 | ruleset_fd = create_ruleset(_metadata, handled2, layer2); | |
3658 | ASSERT_LE(0, ruleset_fd); | |
3659 | enforce_ruleset(_metadata, ruleset_fd); | |
3660 | ASSERT_EQ(0, close(ruleset_fd)); | |
3661 | ||
3662 | fd_layer2 = open(path, O_WRONLY); | |
3663 | EXPECT_EQ(0, test_ftruncate(fd_layer0)); | |
3664 | EXPECT_EQ(0, test_ftruncate(fd_layer1)); | |
3665 | EXPECT_EQ(0, test_ftruncate(fd_layer2)); | |
3666 | ||
3667 | ruleset_fd = create_ruleset(_metadata, handled3, layer3); | |
3668 | ASSERT_LE(0, ruleset_fd); | |
3669 | enforce_ruleset(_metadata, ruleset_fd); | |
3670 | ASSERT_EQ(0, close(ruleset_fd)); | |
3671 | ||
3672 | fd_layer3 = open(path, O_WRONLY); | |
3673 | EXPECT_EQ(0, test_ftruncate(fd_layer0)); | |
3674 | EXPECT_EQ(0, test_ftruncate(fd_layer1)); | |
3675 | EXPECT_EQ(0, test_ftruncate(fd_layer2)); | |
3676 | EXPECT_EQ(EACCES, test_ftruncate(fd_layer3)); | |
3677 | ||
3678 | ASSERT_EQ(0, close(fd_layer0)); | |
3679 | ASSERT_EQ(0, close(fd_layer1)); | |
3680 | ASSERT_EQ(0, close(fd_layer2)); | |
3681 | ASSERT_EQ(0, close(fd_layer3)); | |
3682 | } | |
3683 | ||
41729af2 GN |
3684 | /* clang-format off */ |
3685 | FIXTURE(ftruncate) {}; | |
3686 | /* clang-format on */ | |
3687 | ||
3688 | FIXTURE_SETUP(ftruncate) | |
3689 | { | |
3690 | prepare_layout(_metadata); | |
3691 | create_file(_metadata, file1_s1d1); | |
3692 | } | |
3693 | ||
3694 | FIXTURE_TEARDOWN(ftruncate) | |
3695 | { | |
3696 | EXPECT_EQ(0, remove_path(file1_s1d1)); | |
3697 | cleanup_layout(_metadata); | |
3698 | } | |
3699 | ||
3700 | FIXTURE_VARIANT(ftruncate) | |
3701 | { | |
3702 | const __u64 handled; | |
b838dd76 | 3703 | const __u64 allowed; |
41729af2 GN |
3704 | const int expected_open_result; |
3705 | const int expected_ftruncate_result; | |
3706 | }; | |
3707 | ||
3708 | /* clang-format off */ | |
3709 | FIXTURE_VARIANT_ADD(ftruncate, w_w) { | |
3710 | /* clang-format on */ | |
3711 | .handled = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
b838dd76 | 3712 | .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE, |
41729af2 GN |
3713 | .expected_open_result = 0, |
3714 | .expected_ftruncate_result = 0, | |
3715 | }; | |
3716 | ||
3717 | /* clang-format off */ | |
3718 | FIXTURE_VARIANT_ADD(ftruncate, t_t) { | |
3719 | /* clang-format on */ | |
3720 | .handled = LANDLOCK_ACCESS_FS_TRUNCATE, | |
b838dd76 | 3721 | .allowed = LANDLOCK_ACCESS_FS_TRUNCATE, |
41729af2 GN |
3722 | .expected_open_result = 0, |
3723 | .expected_ftruncate_result = 0, | |
3724 | }; | |
3725 | ||
3726 | /* clang-format off */ | |
3727 | FIXTURE_VARIANT_ADD(ftruncate, wt_w) { | |
3728 | /* clang-format on */ | |
3729 | .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, | |
b838dd76 | 3730 | .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE, |
41729af2 GN |
3731 | .expected_open_result = 0, |
3732 | .expected_ftruncate_result = EACCES, | |
3733 | }; | |
3734 | ||
3735 | /* clang-format off */ | |
3736 | FIXTURE_VARIANT_ADD(ftruncate, wt_wt) { | |
3737 | /* clang-format on */ | |
3738 | .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, | |
b838dd76 | 3739 | .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, |
41729af2 GN |
3740 | .expected_open_result = 0, |
3741 | .expected_ftruncate_result = 0, | |
3742 | }; | |
3743 | ||
3744 | /* clang-format off */ | |
3745 | FIXTURE_VARIANT_ADD(ftruncate, wt_t) { | |
3746 | /* clang-format on */ | |
3747 | .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, | |
b838dd76 | 3748 | .allowed = LANDLOCK_ACCESS_FS_TRUNCATE, |
41729af2 GN |
3749 | .expected_open_result = EACCES, |
3750 | }; | |
3751 | ||
3752 | TEST_F_FORK(ftruncate, open_and_ftruncate) | |
3753 | { | |
3754 | const char *const path = file1_s1d1; | |
3755 | const struct rule rules[] = { | |
3756 | { | |
3757 | .path = path, | |
b838dd76 | 3758 | .access = variant->allowed, |
41729af2 GN |
3759 | }, |
3760 | {}, | |
3761 | }; | |
3762 | int fd, ruleset_fd; | |
3763 | ||
3764 | /* Enable Landlock. */ | |
3765 | ruleset_fd = create_ruleset(_metadata, variant->handled, rules); | |
3766 | ASSERT_LE(0, ruleset_fd); | |
3767 | enforce_ruleset(_metadata, ruleset_fd); | |
3768 | ASSERT_EQ(0, close(ruleset_fd)); | |
3769 | ||
3770 | fd = open(path, O_WRONLY); | |
3771 | EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); | |
3772 | if (fd >= 0) { | |
3773 | EXPECT_EQ(variant->expected_ftruncate_result, | |
3774 | test_ftruncate(fd)); | |
3775 | ASSERT_EQ(0, close(fd)); | |
3776 | } | |
3777 | } | |
3778 | ||
a1a202a5 GN |
3779 | TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes) |
3780 | { | |
3781 | int child, fd, status; | |
3782 | int socket_fds[2]; | |
3783 | ||
3784 | ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, | |
3785 | socket_fds)); | |
3786 | ||
3787 | child = fork(); | |
3788 | ASSERT_LE(0, child); | |
3789 | if (child == 0) { | |
3790 | /* | |
3791 | * Enables Landlock in the child process, open a file descriptor | |
3792 | * where truncation is forbidden and send it to the | |
3793 | * non-landlocked parent process. | |
3794 | */ | |
3795 | const char *const path = file1_s1d1; | |
3796 | const struct rule rules[] = { | |
3797 | { | |
3798 | .path = path, | |
b838dd76 | 3799 | .access = variant->allowed, |
a1a202a5 GN |
3800 | }, |
3801 | {}, | |
3802 | }; | |
3803 | int fd, ruleset_fd; | |
3804 | ||
3805 | ruleset_fd = create_ruleset(_metadata, variant->handled, rules); | |
3806 | ASSERT_LE(0, ruleset_fd); | |
3807 | enforce_ruleset(_metadata, ruleset_fd); | |
3808 | ASSERT_EQ(0, close(ruleset_fd)); | |
3809 | ||
3810 | fd = open(path, O_WRONLY); | |
3811 | ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); | |
3812 | ||
3813 | if (fd >= 0) { | |
3814 | ASSERT_EQ(0, send_fd(socket_fds[0], fd)); | |
3815 | ASSERT_EQ(0, close(fd)); | |
3816 | } | |
3817 | ||
3818 | ASSERT_EQ(0, close(socket_fds[0])); | |
3819 | ||
69fe8ec4 | 3820 | _exit(_metadata->exit_code); |
a1a202a5 GN |
3821 | return; |
3822 | } | |
3823 | ||
3824 | if (variant->expected_open_result == 0) { | |
3825 | fd = recv_fd(socket_fds[1]); | |
3826 | ASSERT_LE(0, fd); | |
3827 | ||
3828 | EXPECT_EQ(variant->expected_ftruncate_result, | |
3829 | test_ftruncate(fd)); | |
3830 | ASSERT_EQ(0, close(fd)); | |
3831 | } | |
3832 | ||
3833 | ASSERT_EQ(child, waitpid(child, &status, 0)); | |
3834 | ASSERT_EQ(1, WIFEXITED(status)); | |
3835 | ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); | |
3836 | ||
3837 | ASSERT_EQ(0, close(socket_fds[0])); | |
3838 | ASSERT_EQ(0, close(socket_fds[1])); | |
3839 | } | |
3840 | ||
0d8c658b GN |
3841 | TEST(memfd_ftruncate) |
3842 | { | |
3843 | int fd; | |
3844 | ||
3845 | fd = memfd_create("name", MFD_CLOEXEC); | |
3846 | ASSERT_LE(0, fd); | |
3847 | ||
3848 | /* | |
3849 | * Checks that ftruncate is permitted on file descriptors that are | |
3850 | * created in ways other than open(2). | |
3851 | */ | |
3852 | EXPECT_EQ(0, test_ftruncate(fd)); | |
3853 | ||
3854 | ASSERT_EQ(0, close(fd)); | |
3855 | } | |
3856 | ||
4598d9ab MS |
3857 | /* clang-format off */ |
3858 | FIXTURE(layout1_bind) {}; | |
3859 | /* clang-format on */ | |
e1199815 MS |
3860 | |
3861 | FIXTURE_SETUP(layout1_bind) | |
3862 | { | |
3863 | prepare_layout(_metadata); | |
3864 | ||
3865 | create_layout1(_metadata); | |
3866 | ||
3867 | set_cap(_metadata, CAP_SYS_ADMIN); | |
3868 | ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL)); | |
3869 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
3870 | } | |
3871 | ||
3872 | FIXTURE_TEARDOWN(layout1_bind) | |
3873 | { | |
41cca054 | 3874 | /* umount(dir_s2d2)) is handled by namespace lifetime. */ |
e1199815 MS |
3875 | |
3876 | remove_layout1(_metadata); | |
3877 | ||
3878 | cleanup_layout(_metadata); | |
3879 | } | |
3880 | ||
3881 | static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3"; | |
3882 | static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1"; | |
3883 | ||
3884 | /* | |
3885 | * layout1_bind hierarchy: | |
3886 | * | |
3887 | * tmp | |
3888 | * ├── s1d1 | |
3889 | * │ ├── f1 | |
3890 | * │ ├── f2 | |
3891 | * │ └── s1d2 | |
3892 | * │ ├── f1 | |
3893 | * │ ├── f2 | |
3894 | * │ └── s1d3 | |
3895 | * │ ├── f1 | |
3896 | * │ └── f2 | |
3897 | * ├── s2d1 | |
3898 | * │ ├── f1 | |
3899 | * │ └── s2d2 | |
3900 | * │ ├── f1 | |
3901 | * │ ├── f2 | |
3902 | * │ └── s1d3 | |
3903 | * │ ├── f1 | |
3904 | * │ └── f2 | |
3905 | * └── s3d1 | |
3906 | * └── s3d2 | |
3907 | * └── s3d3 | |
3908 | */ | |
3909 | ||
3910 | TEST_F_FORK(layout1_bind, no_restriction) | |
3911 | { | |
3912 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | |
3913 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | |
3914 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | |
3915 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
3916 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | |
3917 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
3918 | ||
3919 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); | |
3920 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); | |
3921 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); | |
3922 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); | |
3923 | ASSERT_EQ(ENOENT, test_open(dir_s2d3, O_RDONLY)); | |
3924 | ASSERT_EQ(ENOENT, test_open(file1_s2d3, O_RDONLY)); | |
3925 | ||
3926 | ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY)); | |
3927 | ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); | |
3928 | ||
3929 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | |
3930 | } | |
3931 | ||
3932 | TEST_F_FORK(layout1_bind, same_content_same_file) | |
3933 | { | |
3934 | /* | |
3935 | * Sets access right on parent directories of both source and | |
3936 | * destination mount points. | |
3937 | */ | |
3938 | const struct rule layer1_parent[] = { | |
3939 | { | |
3940 | .path = dir_s1d1, | |
3941 | .access = ACCESS_RO, | |
3942 | }, | |
3943 | { | |
3944 | .path = dir_s2d1, | |
3945 | .access = ACCESS_RW, | |
3946 | }, | |
135464f9 | 3947 | {}, |
e1199815 MS |
3948 | }; |
3949 | /* | |
3950 | * Sets access rights on the same bind-mounted directories. The result | |
3951 | * should be ACCESS_RW for both directories, but not both hierarchies | |
3952 | * because of the first layer. | |
3953 | */ | |
3954 | const struct rule layer2_mount_point[] = { | |
3955 | { | |
3956 | .path = dir_s1d2, | |
3957 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
3958 | }, | |
3959 | { | |
3960 | .path = dir_s2d2, | |
3961 | .access = ACCESS_RW, | |
3962 | }, | |
135464f9 | 3963 | {}, |
e1199815 MS |
3964 | }; |
3965 | /* Only allow read-access to the s1d3 hierarchies. */ | |
3966 | const struct rule layer3_source[] = { | |
3967 | { | |
3968 | .path = dir_s1d3, | |
3969 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
3970 | }, | |
135464f9 | 3971 | {}, |
e1199815 MS |
3972 | }; |
3973 | /* Removes all access rights. */ | |
3974 | const struct rule layer4_destination[] = { | |
3975 | { | |
3976 | .path = bind_file1_s1d3, | |
3977 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | |
3978 | }, | |
135464f9 | 3979 | {}, |
e1199815 MS |
3980 | }; |
3981 | int ruleset_fd; | |
3982 | ||
3983 | /* Sets rules for the parent directories. */ | |
3984 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent); | |
3985 | ASSERT_LE(0, ruleset_fd); | |
3986 | enforce_ruleset(_metadata, ruleset_fd); | |
3987 | ASSERT_EQ(0, close(ruleset_fd)); | |
3988 | ||
3989 | /* Checks source hierarchy. */ | |
3990 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | |
3991 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
3992 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
3993 | ||
3994 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
3995 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
3996 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
3997 | ||
3998 | /* Checks destination hierarchy. */ | |
3999 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDWR)); | |
4000 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); | |
4001 | ||
4002 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); | |
4003 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | |
4004 | ||
4005 | /* Sets rules for the mount points. */ | |
4006 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point); | |
4007 | ASSERT_LE(0, ruleset_fd); | |
4008 | enforce_ruleset(_metadata, ruleset_fd); | |
4009 | ASSERT_EQ(0, close(ruleset_fd)); | |
4010 | ||
4011 | /* Checks source hierarchy. */ | |
4012 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | |
4013 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | |
4014 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | |
4015 | ||
4016 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | |
4017 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
4018 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
4019 | ||
4020 | /* Checks destination hierarchy. */ | |
4021 | ASSERT_EQ(EACCES, test_open(file1_s2d1, O_RDONLY)); | |
4022 | ASSERT_EQ(EACCES, test_open(file1_s2d1, O_WRONLY)); | |
4023 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); | |
4024 | ||
4025 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); | |
4026 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | |
4027 | ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
4028 | ||
4029 | /* Sets a (shared) rule only on the source. */ | |
4030 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source); | |
4031 | ASSERT_LE(0, ruleset_fd); | |
4032 | enforce_ruleset(_metadata, ruleset_fd); | |
4033 | ASSERT_EQ(0, close(ruleset_fd)); | |
4034 | ||
4035 | /* Checks source hierarchy. */ | |
4036 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); | |
4037 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | |
4038 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | |
4039 | ||
4040 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | |
4041 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
4042 | ASSERT_EQ(EACCES, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
4043 | ||
4044 | /* Checks destination hierarchy. */ | |
4045 | ASSERT_EQ(EACCES, test_open(file1_s2d2, O_RDONLY)); | |
4046 | ASSERT_EQ(EACCES, test_open(file1_s2d2, O_WRONLY)); | |
4047 | ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | |
4048 | ||
4049 | ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); | |
4050 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); | |
4051 | ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); | |
4052 | ||
4053 | /* Sets a (shared) rule only on the destination. */ | |
4054 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination); | |
4055 | ASSERT_LE(0, ruleset_fd); | |
4056 | enforce_ruleset(_metadata, ruleset_fd); | |
4057 | ASSERT_EQ(0, close(ruleset_fd)); | |
4058 | ||
4059 | /* Checks source hierarchy. */ | |
4060 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); | |
4061 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | |
4062 | ||
4063 | /* Checks destination hierarchy. */ | |
4064 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_RDONLY)); | |
4065 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); | |
4066 | } | |
4067 | ||
f4056b92 MS |
4068 | TEST_F_FORK(layout1_bind, reparent_cross_mount) |
4069 | { | |
4070 | const struct rule layer1[] = { | |
4071 | { | |
4072 | /* dir_s2d1 is beneath the dir_s2d2 mount point. */ | |
4073 | .path = dir_s2d1, | |
4074 | .access = LANDLOCK_ACCESS_FS_REFER, | |
4075 | }, | |
4076 | { | |
4077 | .path = bind_dir_s1d3, | |
4078 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | |
4079 | }, | |
4080 | {}, | |
4081 | }; | |
4082 | int ruleset_fd = create_ruleset( | |
4083 | _metadata, | |
4084 | LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1); | |
4085 | ||
4086 | ASSERT_LE(0, ruleset_fd); | |
4087 | enforce_ruleset(_metadata, ruleset_fd); | |
4088 | ASSERT_EQ(0, close(ruleset_fd)); | |
4089 | ||
4090 | /* Checks basic denied move. */ | |
4091 | ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2)); | |
4092 | ASSERT_EQ(EXDEV, errno); | |
4093 | ||
4094 | /* Checks real cross-mount move (Landlock is not involved). */ | |
4095 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s2d2)); | |
4096 | ASSERT_EQ(EXDEV, errno); | |
4097 | ||
4098 | /* Checks move that will give more accesses. */ | |
4099 | ASSERT_EQ(-1, rename(file1_s2d2, bind_file1_s1d3)); | |
4100 | ASSERT_EQ(EXDEV, errno); | |
4101 | ||
4102 | /* Checks legitimate downgrade move. */ | |
4103 | ASSERT_EQ(0, rename(bind_file1_s1d3, file1_s2d2)); | |
4104 | } | |
4105 | ||
371183fa MS |
4106 | #define LOWER_BASE TMP_DIR "/lower" |
4107 | #define LOWER_DATA LOWER_BASE "/data" | |
e1199815 MS |
4108 | static const char lower_fl1[] = LOWER_DATA "/fl1"; |
4109 | static const char lower_dl1[] = LOWER_DATA "/dl1"; | |
4110 | static const char lower_dl1_fl2[] = LOWER_DATA "/dl1/fl2"; | |
4111 | static const char lower_fo1[] = LOWER_DATA "/fo1"; | |
4112 | static const char lower_do1[] = LOWER_DATA "/do1"; | |
4113 | static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2"; | |
4114 | static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3"; | |
4115 | ||
4116 | static const char (*lower_base_files[])[] = { | |
4117 | &lower_fl1, | |
4118 | &lower_fo1, | |
135464f9 | 4119 | NULL, |
e1199815 MS |
4120 | }; |
4121 | static const char (*lower_base_directories[])[] = { | |
4122 | &lower_dl1, | |
4123 | &lower_do1, | |
135464f9 | 4124 | NULL, |
e1199815 MS |
4125 | }; |
4126 | static const char (*lower_sub_files[])[] = { | |
4127 | &lower_dl1_fl2, | |
4128 | &lower_do1_fo2, | |
4129 | &lower_do1_fl3, | |
135464f9 | 4130 | NULL, |
e1199815 MS |
4131 | }; |
4132 | ||
371183fa MS |
4133 | #define UPPER_BASE TMP_DIR "/upper" |
4134 | #define UPPER_DATA UPPER_BASE "/data" | |
4135 | #define UPPER_WORK UPPER_BASE "/work" | |
e1199815 MS |
4136 | static const char upper_fu1[] = UPPER_DATA "/fu1"; |
4137 | static const char upper_du1[] = UPPER_DATA "/du1"; | |
4138 | static const char upper_du1_fu2[] = UPPER_DATA "/du1/fu2"; | |
4139 | static const char upper_fo1[] = UPPER_DATA "/fo1"; | |
4140 | static const char upper_do1[] = UPPER_DATA "/do1"; | |
4141 | static const char upper_do1_fo2[] = UPPER_DATA "/do1/fo2"; | |
4142 | static const char upper_do1_fu3[] = UPPER_DATA "/do1/fu3"; | |
4143 | ||
4144 | static const char (*upper_base_files[])[] = { | |
4145 | &upper_fu1, | |
4146 | &upper_fo1, | |
135464f9 | 4147 | NULL, |
e1199815 MS |
4148 | }; |
4149 | static const char (*upper_base_directories[])[] = { | |
4150 | &upper_du1, | |
4151 | &upper_do1, | |
135464f9 | 4152 | NULL, |
e1199815 MS |
4153 | }; |
4154 | static const char (*upper_sub_files[])[] = { | |
4155 | &upper_du1_fu2, | |
4156 | &upper_do1_fo2, | |
4157 | &upper_do1_fu3, | |
135464f9 | 4158 | NULL, |
e1199815 MS |
4159 | }; |
4160 | ||
371183fa MS |
4161 | #define MERGE_BASE TMP_DIR "/merge" |
4162 | #define MERGE_DATA MERGE_BASE "/data" | |
e1199815 MS |
4163 | static const char merge_fl1[] = MERGE_DATA "/fl1"; |
4164 | static const char merge_dl1[] = MERGE_DATA "/dl1"; | |
4165 | static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2"; | |
4166 | static const char merge_fu1[] = MERGE_DATA "/fu1"; | |
4167 | static const char merge_du1[] = MERGE_DATA "/du1"; | |
4168 | static const char merge_du1_fu2[] = MERGE_DATA "/du1/fu2"; | |
4169 | static const char merge_fo1[] = MERGE_DATA "/fo1"; | |
4170 | static const char merge_do1[] = MERGE_DATA "/do1"; | |
4171 | static const char merge_do1_fo2[] = MERGE_DATA "/do1/fo2"; | |
4172 | static const char merge_do1_fl3[] = MERGE_DATA "/do1/fl3"; | |
4173 | static const char merge_do1_fu3[] = MERGE_DATA "/do1/fu3"; | |
4174 | ||
4175 | static const char (*merge_base_files[])[] = { | |
4176 | &merge_fl1, | |
4177 | &merge_fu1, | |
4178 | &merge_fo1, | |
135464f9 | 4179 | NULL, |
e1199815 MS |
4180 | }; |
4181 | static const char (*merge_base_directories[])[] = { | |
4182 | &merge_dl1, | |
4183 | &merge_du1, | |
4184 | &merge_do1, | |
135464f9 | 4185 | NULL, |
e1199815 MS |
4186 | }; |
4187 | static const char (*merge_sub_files[])[] = { | |
371183fa MS |
4188 | &merge_dl1_fl2, &merge_du1_fu2, &merge_do1_fo2, |
4189 | &merge_do1_fl3, &merge_do1_fu3, NULL, | |
e1199815 MS |
4190 | }; |
4191 | ||
4192 | /* | |
4193 | * layout2_overlay hierarchy: | |
4194 | * | |
4195 | * tmp | |
4196 | * ├── lower | |
4197 | * │ └── data | |
4198 | * │ ├── dl1 | |
4199 | * │ │ └── fl2 | |
4200 | * │ ├── do1 | |
4201 | * │ │ ├── fl3 | |
4202 | * │ │ └── fo2 | |
4203 | * │ ├── fl1 | |
4204 | * │ └── fo1 | |
4205 | * ├── merge | |
4206 | * │ └── data | |
4207 | * │ ├── dl1 | |
4208 | * │ │ └── fl2 | |
4209 | * │ ├── do1 | |
4210 | * │ │ ├── fl3 | |
4211 | * │ │ ├── fo2 | |
4212 | * │ │ └── fu3 | |
4213 | * │ ├── du1 | |
4214 | * │ │ └── fu2 | |
4215 | * │ ├── fl1 | |
4216 | * │ ├── fo1 | |
4217 | * │ └── fu1 | |
4218 | * └── upper | |
4219 | * ├── data | |
4220 | * │ ├── do1 | |
4221 | * │ │ ├── fo2 | |
4222 | * │ │ └── fu3 | |
4223 | * │ ├── du1 | |
4224 | * │ │ └── fu2 | |
4225 | * │ ├── fo1 | |
4226 | * │ └── fu1 | |
4227 | * └── work | |
4228 | * └── work | |
4229 | */ | |
4230 | ||
3de64b65 MS |
4231 | FIXTURE(layout2_overlay) |
4232 | { | |
4233 | bool skip_test; | |
4234 | }; | |
e1199815 MS |
4235 | |
4236 | FIXTURE_SETUP(layout2_overlay) | |
4237 | { | |
3de64b65 MS |
4238 | if (!supports_filesystem("overlay")) { |
4239 | self->skip_test = true; | |
4240 | SKIP(return, "overlayfs is not supported (setup)"); | |
4241 | } | |
366617a6 | 4242 | |
e1199815 MS |
4243 | prepare_layout(_metadata); |
4244 | ||
4245 | create_directory(_metadata, LOWER_BASE); | |
4246 | set_cap(_metadata, CAP_SYS_ADMIN); | |
4247 | /* Creates tmpfs mount points to get deterministic overlayfs. */ | |
55ab3fbe | 4248 | ASSERT_EQ(0, mount_opt(&mnt_tmp, LOWER_BASE)); |
e1199815 MS |
4249 | clear_cap(_metadata, CAP_SYS_ADMIN); |
4250 | create_file(_metadata, lower_fl1); | |
4251 | create_file(_metadata, lower_dl1_fl2); | |
4252 | create_file(_metadata, lower_fo1); | |
4253 | create_file(_metadata, lower_do1_fo2); | |
4254 | create_file(_metadata, lower_do1_fl3); | |
4255 | ||
4256 | create_directory(_metadata, UPPER_BASE); | |
4257 | set_cap(_metadata, CAP_SYS_ADMIN); | |
55ab3fbe | 4258 | ASSERT_EQ(0, mount_opt(&mnt_tmp, UPPER_BASE)); |
e1199815 MS |
4259 | clear_cap(_metadata, CAP_SYS_ADMIN); |
4260 | create_file(_metadata, upper_fu1); | |
4261 | create_file(_metadata, upper_du1_fu2); | |
4262 | create_file(_metadata, upper_fo1); | |
4263 | create_file(_metadata, upper_do1_fo2); | |
4264 | create_file(_metadata, upper_do1_fu3); | |
4265 | ASSERT_EQ(0, mkdir(UPPER_WORK, 0700)); | |
4266 | ||
4267 | create_directory(_metadata, MERGE_DATA); | |
4268 | set_cap(_metadata, CAP_SYS_ADMIN); | |
4269 | set_cap(_metadata, CAP_DAC_OVERRIDE); | |
4270 | ASSERT_EQ(0, mount("overlay", MERGE_DATA, "overlay", 0, | |
371183fa MS |
4271 | "lowerdir=" LOWER_DATA ",upperdir=" UPPER_DATA |
4272 | ",workdir=" UPPER_WORK)); | |
e1199815 MS |
4273 | clear_cap(_metadata, CAP_DAC_OVERRIDE); |
4274 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
4275 | } | |
4276 | ||
4277 | FIXTURE_TEARDOWN(layout2_overlay) | |
4278 | { | |
3de64b65 MS |
4279 | if (self->skip_test) |
4280 | SKIP(return, "overlayfs is not supported (teardown)"); | |
366617a6 | 4281 | |
e1199815 MS |
4282 | EXPECT_EQ(0, remove_path(lower_do1_fl3)); |
4283 | EXPECT_EQ(0, remove_path(lower_dl1_fl2)); | |
4284 | EXPECT_EQ(0, remove_path(lower_fl1)); | |
4285 | EXPECT_EQ(0, remove_path(lower_do1_fo2)); | |
4286 | EXPECT_EQ(0, remove_path(lower_fo1)); | |
41cca054 MS |
4287 | |
4288 | /* umount(LOWER_BASE)) is handled by namespace lifetime. */ | |
e1199815 MS |
4289 | EXPECT_EQ(0, remove_path(LOWER_BASE)); |
4290 | ||
4291 | EXPECT_EQ(0, remove_path(upper_do1_fu3)); | |
4292 | EXPECT_EQ(0, remove_path(upper_du1_fu2)); | |
4293 | EXPECT_EQ(0, remove_path(upper_fu1)); | |
4294 | EXPECT_EQ(0, remove_path(upper_do1_fo2)); | |
4295 | EXPECT_EQ(0, remove_path(upper_fo1)); | |
4296 | EXPECT_EQ(0, remove_path(UPPER_WORK "/work")); | |
41cca054 MS |
4297 | |
4298 | /* umount(UPPER_BASE)) is handled by namespace lifetime. */ | |
e1199815 MS |
4299 | EXPECT_EQ(0, remove_path(UPPER_BASE)); |
4300 | ||
41cca054 | 4301 | /* umount(MERGE_DATA)) is handled by namespace lifetime. */ |
e1199815 MS |
4302 | EXPECT_EQ(0, remove_path(MERGE_DATA)); |
4303 | ||
4304 | cleanup_layout(_metadata); | |
4305 | } | |
4306 | ||
4307 | TEST_F_FORK(layout2_overlay, no_restriction) | |
4308 | { | |
3de64b65 MS |
4309 | if (self->skip_test) |
4310 | SKIP(return, "overlayfs is not supported (test)"); | |
366617a6 | 4311 | |
e1199815 MS |
4312 | ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY)); |
4313 | ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY)); | |
4314 | ASSERT_EQ(0, test_open(lower_dl1_fl2, O_RDONLY)); | |
4315 | ASSERT_EQ(0, test_open(lower_fo1, O_RDONLY)); | |
4316 | ASSERT_EQ(0, test_open(lower_do1, O_RDONLY)); | |
4317 | ASSERT_EQ(0, test_open(lower_do1_fo2, O_RDONLY)); | |
4318 | ASSERT_EQ(0, test_open(lower_do1_fl3, O_RDONLY)); | |
4319 | ||
4320 | ASSERT_EQ(0, test_open(upper_fu1, O_RDONLY)); | |
4321 | ASSERT_EQ(0, test_open(upper_du1, O_RDONLY)); | |
4322 | ASSERT_EQ(0, test_open(upper_du1_fu2, O_RDONLY)); | |
4323 | ASSERT_EQ(0, test_open(upper_fo1, O_RDONLY)); | |
4324 | ASSERT_EQ(0, test_open(upper_do1, O_RDONLY)); | |
4325 | ASSERT_EQ(0, test_open(upper_do1_fo2, O_RDONLY)); | |
4326 | ASSERT_EQ(0, test_open(upper_do1_fu3, O_RDONLY)); | |
4327 | ||
4328 | ASSERT_EQ(0, test_open(merge_fl1, O_RDONLY)); | |
4329 | ASSERT_EQ(0, test_open(merge_dl1, O_RDONLY)); | |
4330 | ASSERT_EQ(0, test_open(merge_dl1_fl2, O_RDONLY)); | |
4331 | ASSERT_EQ(0, test_open(merge_fu1, O_RDONLY)); | |
4332 | ASSERT_EQ(0, test_open(merge_du1, O_RDONLY)); | |
4333 | ASSERT_EQ(0, test_open(merge_du1_fu2, O_RDONLY)); | |
4334 | ASSERT_EQ(0, test_open(merge_fo1, O_RDONLY)); | |
4335 | ASSERT_EQ(0, test_open(merge_do1, O_RDONLY)); | |
4336 | ASSERT_EQ(0, test_open(merge_do1_fo2, O_RDONLY)); | |
4337 | ASSERT_EQ(0, test_open(merge_do1_fl3, O_RDONLY)); | |
4338 | ASSERT_EQ(0, test_open(merge_do1_fu3, O_RDONLY)); | |
4339 | } | |
4340 | ||
371183fa MS |
4341 | #define for_each_path(path_list, path_entry, i) \ |
4342 | for (i = 0, path_entry = *path_list[i]; path_list[i]; \ | |
4343 | path_entry = *path_list[++i]) | |
e1199815 MS |
4344 | |
4345 | TEST_F_FORK(layout2_overlay, same_content_different_file) | |
4346 | { | |
4347 | /* Sets access right on parent directories of both layers. */ | |
4348 | const struct rule layer1_base[] = { | |
4349 | { | |
4350 | .path = LOWER_BASE, | |
4351 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4352 | }, | |
4353 | { | |
4354 | .path = UPPER_BASE, | |
4355 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4356 | }, | |
4357 | { | |
4358 | .path = MERGE_BASE, | |
4359 | .access = ACCESS_RW, | |
4360 | }, | |
135464f9 | 4361 | {}, |
e1199815 MS |
4362 | }; |
4363 | const struct rule layer2_data[] = { | |
4364 | { | |
4365 | .path = LOWER_DATA, | |
4366 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4367 | }, | |
4368 | { | |
4369 | .path = UPPER_DATA, | |
4370 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4371 | }, | |
4372 | { | |
4373 | .path = MERGE_DATA, | |
4374 | .access = ACCESS_RW, | |
4375 | }, | |
135464f9 | 4376 | {}, |
e1199815 MS |
4377 | }; |
4378 | /* Sets access right on directories inside both layers. */ | |
4379 | const struct rule layer3_subdirs[] = { | |
4380 | { | |
4381 | .path = lower_dl1, | |
4382 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4383 | }, | |
4384 | { | |
4385 | .path = lower_do1, | |
4386 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4387 | }, | |
4388 | { | |
4389 | .path = upper_du1, | |
4390 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4391 | }, | |
4392 | { | |
4393 | .path = upper_do1, | |
4394 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4395 | }, | |
4396 | { | |
4397 | .path = merge_dl1, | |
4398 | .access = ACCESS_RW, | |
4399 | }, | |
4400 | { | |
4401 | .path = merge_du1, | |
4402 | .access = ACCESS_RW, | |
4403 | }, | |
4404 | { | |
4405 | .path = merge_do1, | |
4406 | .access = ACCESS_RW, | |
4407 | }, | |
135464f9 | 4408 | {}, |
e1199815 MS |
4409 | }; |
4410 | /* Tighten access rights to the files. */ | |
4411 | const struct rule layer4_files[] = { | |
4412 | { | |
4413 | .path = lower_dl1_fl2, | |
4414 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4415 | }, | |
4416 | { | |
4417 | .path = lower_do1_fo2, | |
4418 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4419 | }, | |
4420 | { | |
4421 | .path = lower_do1_fl3, | |
4422 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4423 | }, | |
4424 | { | |
4425 | .path = upper_du1_fu2, | |
4426 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4427 | }, | |
4428 | { | |
4429 | .path = upper_do1_fo2, | |
4430 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4431 | }, | |
4432 | { | |
4433 | .path = upper_do1_fu3, | |
4434 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4435 | }, | |
4436 | { | |
4437 | .path = merge_dl1_fl2, | |
4438 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4439 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
4440 | }, |
4441 | { | |
4442 | .path = merge_du1_fu2, | |
4443 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4444 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
4445 | }, |
4446 | { | |
4447 | .path = merge_do1_fo2, | |
4448 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4449 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
4450 | }, |
4451 | { | |
4452 | .path = merge_do1_fl3, | |
4453 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4454 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 MS |
4455 | }, |
4456 | { | |
4457 | .path = merge_do1_fu3, | |
4458 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4459 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 | 4460 | }, |
135464f9 | 4461 | {}, |
e1199815 MS |
4462 | }; |
4463 | const struct rule layer5_merge_only[] = { | |
4464 | { | |
4465 | .path = MERGE_DATA, | |
4466 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | |
371183fa | 4467 | LANDLOCK_ACCESS_FS_WRITE_FILE, |
e1199815 | 4468 | }, |
135464f9 | 4469 | {}, |
e1199815 MS |
4470 | }; |
4471 | int ruleset_fd; | |
4472 | size_t i; | |
4473 | const char *path_entry; | |
366617a6 | 4474 | |
3de64b65 MS |
4475 | if (self->skip_test) |
4476 | SKIP(return, "overlayfs is not supported (test)"); | |
e1199815 MS |
4477 | |
4478 | /* Sets rules on base directories (i.e. outside overlay scope). */ | |
4479 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); | |
4480 | ASSERT_LE(0, ruleset_fd); | |
4481 | enforce_ruleset(_metadata, ruleset_fd); | |
4482 | ASSERT_EQ(0, close(ruleset_fd)); | |
4483 | ||
4484 | /* Checks lower layer. */ | |
4485 | for_each_path(lower_base_files, path_entry, i) { | |
4486 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4487 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4488 | } | |
4489 | for_each_path(lower_base_directories, path_entry, i) { | |
371183fa MS |
4490 | ASSERT_EQ(EACCES, |
4491 | test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
e1199815 MS |
4492 | } |
4493 | for_each_path(lower_sub_files, path_entry, i) { | |
4494 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4495 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4496 | } | |
4497 | /* Checks upper layer. */ | |
4498 | for_each_path(upper_base_files, path_entry, i) { | |
4499 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4500 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4501 | } | |
4502 | for_each_path(upper_base_directories, path_entry, i) { | |
371183fa MS |
4503 | ASSERT_EQ(EACCES, |
4504 | test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
e1199815 MS |
4505 | } |
4506 | for_each_path(upper_sub_files, path_entry, i) { | |
4507 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4508 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4509 | } | |
4510 | /* | |
4511 | * Checks that access rights are independent from the lower and upper | |
4512 | * layers: write access to upper files viewed through the merge point | |
4513 | * is still allowed, and write access to lower file viewed (and copied) | |
4514 | * through the merge point is still allowed. | |
4515 | */ | |
4516 | for_each_path(merge_base_files, path_entry, i) { | |
4517 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4518 | } | |
4519 | for_each_path(merge_base_directories, path_entry, i) { | |
4520 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
4521 | } | |
4522 | for_each_path(merge_sub_files, path_entry, i) { | |
4523 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4524 | } | |
4525 | ||
4526 | /* Sets rules on data directories (i.e. inside overlay scope). */ | |
4527 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data); | |
4528 | ASSERT_LE(0, ruleset_fd); | |
4529 | enforce_ruleset(_metadata, ruleset_fd); | |
4530 | ASSERT_EQ(0, close(ruleset_fd)); | |
4531 | ||
4532 | /* Checks merge. */ | |
4533 | for_each_path(merge_base_files, path_entry, i) { | |
4534 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4535 | } | |
4536 | for_each_path(merge_base_directories, path_entry, i) { | |
4537 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
4538 | } | |
4539 | for_each_path(merge_sub_files, path_entry, i) { | |
4540 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4541 | } | |
4542 | ||
4543 | /* Same checks with tighter rules. */ | |
4544 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs); | |
4545 | ASSERT_LE(0, ruleset_fd); | |
4546 | enforce_ruleset(_metadata, ruleset_fd); | |
4547 | ASSERT_EQ(0, close(ruleset_fd)); | |
4548 | ||
4549 | /* Checks changes for lower layer. */ | |
4550 | for_each_path(lower_base_files, path_entry, i) { | |
4551 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | |
4552 | } | |
4553 | /* Checks changes for upper layer. */ | |
4554 | for_each_path(upper_base_files, path_entry, i) { | |
4555 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | |
4556 | } | |
4557 | /* Checks all merge accesses. */ | |
4558 | for_each_path(merge_base_files, path_entry, i) { | |
4559 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | |
4560 | } | |
4561 | for_each_path(merge_base_directories, path_entry, i) { | |
4562 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
4563 | } | |
4564 | for_each_path(merge_sub_files, path_entry, i) { | |
4565 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4566 | } | |
4567 | ||
4568 | /* Sets rules directly on overlayed files. */ | |
4569 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files); | |
4570 | ASSERT_LE(0, ruleset_fd); | |
4571 | enforce_ruleset(_metadata, ruleset_fd); | |
4572 | ASSERT_EQ(0, close(ruleset_fd)); | |
4573 | ||
4574 | /* Checks unchanged accesses on lower layer. */ | |
4575 | for_each_path(lower_sub_files, path_entry, i) { | |
4576 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4577 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4578 | } | |
4579 | /* Checks unchanged accesses on upper layer. */ | |
4580 | for_each_path(upper_sub_files, path_entry, i) { | |
4581 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | |
4582 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | |
4583 | } | |
4584 | /* Checks all merge accesses. */ | |
4585 | for_each_path(merge_base_files, path_entry, i) { | |
4586 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | |
4587 | } | |
4588 | for_each_path(merge_base_directories, path_entry, i) { | |
371183fa MS |
4589 | ASSERT_EQ(EACCES, |
4590 | test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
e1199815 MS |
4591 | } |
4592 | for_each_path(merge_sub_files, path_entry, i) { | |
4593 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4594 | } | |
4595 | ||
4596 | /* Only allowes access to the merge hierarchy. */ | |
4597 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only); | |
4598 | ASSERT_LE(0, ruleset_fd); | |
4599 | enforce_ruleset(_metadata, ruleset_fd); | |
4600 | ASSERT_EQ(0, close(ruleset_fd)); | |
4601 | ||
4602 | /* Checks new accesses on lower layer. */ | |
4603 | for_each_path(lower_sub_files, path_entry, i) { | |
4604 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | |
4605 | } | |
4606 | /* Checks new accesses on upper layer. */ | |
4607 | for_each_path(upper_sub_files, path_entry, i) { | |
4608 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | |
4609 | } | |
4610 | /* Checks all merge accesses. */ | |
4611 | for_each_path(merge_base_files, path_entry, i) { | |
4612 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | |
4613 | } | |
4614 | for_each_path(merge_base_directories, path_entry, i) { | |
371183fa MS |
4615 | ASSERT_EQ(EACCES, |
4616 | test_open(path_entry, O_RDONLY | O_DIRECTORY)); | |
e1199815 MS |
4617 | } |
4618 | for_each_path(merge_sub_files, path_entry, i) { | |
4619 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | |
4620 | } | |
4621 | } | |
4622 | ||
04f9070e MS |
4623 | FIXTURE(layout3_fs) |
4624 | { | |
4625 | bool has_created_dir; | |
4626 | bool has_created_file; | |
4627 | char *dir_path; | |
4628 | bool skip_test; | |
4629 | }; | |
4630 | ||
4631 | FIXTURE_VARIANT(layout3_fs) | |
4632 | { | |
4633 | const struct mnt_opt mnt; | |
4634 | const char *const file_path; | |
35ca4239 | 4635 | unsigned int cwd_fs_magic; |
04f9070e MS |
4636 | }; |
4637 | ||
4638 | /* clang-format off */ | |
4639 | FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) { | |
4640 | /* clang-format on */ | |
40b7835e HY |
4641 | .mnt = { |
4642 | .type = "tmpfs", | |
4643 | .data = MNT_TMP_DATA, | |
4644 | }, | |
04f9070e MS |
4645 | .file_path = file1_s1d1, |
4646 | }; | |
4647 | ||
4648 | FIXTURE_VARIANT_ADD(layout3_fs, ramfs) { | |
4649 | .mnt = { | |
4650 | .type = "ramfs", | |
4651 | .data = "mode=700", | |
4652 | }, | |
4653 | .file_path = TMP_DIR "/dir/file", | |
4654 | }; | |
4655 | ||
4656 | FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) { | |
4657 | .mnt = { | |
4658 | .type = "cgroup2", | |
4659 | }, | |
4660 | .file_path = TMP_DIR "/test/cgroup.procs", | |
4661 | }; | |
4662 | ||
4663 | FIXTURE_VARIANT_ADD(layout3_fs, proc) { | |
4664 | .mnt = { | |
4665 | .type = "proc", | |
4666 | }, | |
4667 | .file_path = TMP_DIR "/self/status", | |
4668 | }; | |
4669 | ||
4670 | FIXTURE_VARIANT_ADD(layout3_fs, sysfs) { | |
4671 | .mnt = { | |
4672 | .type = "sysfs", | |
4673 | }, | |
4674 | .file_path = TMP_DIR "/kernel/notes", | |
4675 | }; | |
4676 | ||
35ca4239 MS |
4677 | FIXTURE_VARIANT_ADD(layout3_fs, hostfs) { |
4678 | .mnt = { | |
4679 | .source = TMP_DIR, | |
4680 | .flags = MS_BIND, | |
4681 | }, | |
4682 | .file_path = TMP_DIR "/dir/file", | |
4683 | .cwd_fs_magic = HOSTFS_SUPER_MAGIC, | |
4684 | }; | |
4685 | ||
04f9070e MS |
4686 | FIXTURE_SETUP(layout3_fs) |
4687 | { | |
4688 | struct stat statbuf; | |
4689 | const char *slash; | |
4690 | size_t dir_len; | |
4691 | ||
35ca4239 MS |
4692 | if (!supports_filesystem(variant->mnt.type) || |
4693 | !cwd_matches_fs(variant->cwd_fs_magic)) { | |
04f9070e MS |
4694 | self->skip_test = true; |
4695 | SKIP(return, "this filesystem is not supported (setup)"); | |
4696 | } | |
4697 | ||
41cca054 MS |
4698 | _metadata->teardown_parent = true; |
4699 | ||
04f9070e MS |
4700 | slash = strrchr(variant->file_path, '/'); |
4701 | ASSERT_NE(slash, NULL); | |
4702 | dir_len = (size_t)slash - (size_t)variant->file_path; | |
4703 | ASSERT_LT(0, dir_len); | |
4704 | self->dir_path = malloc(dir_len + 1); | |
4705 | self->dir_path[dir_len] = '\0'; | |
4706 | strncpy(self->dir_path, variant->file_path, dir_len); | |
4707 | ||
4708 | prepare_layout_opt(_metadata, &variant->mnt); | |
4709 | ||
4710 | /* Creates directory when required. */ | |
4711 | if (stat(self->dir_path, &statbuf)) { | |
4712 | set_cap(_metadata, CAP_DAC_OVERRIDE); | |
4713 | EXPECT_EQ(0, mkdir(self->dir_path, 0700)) | |
4714 | { | |
4715 | TH_LOG("Failed to create directory \"%s\": %s", | |
4716 | self->dir_path, strerror(errno)); | |
4717 | free(self->dir_path); | |
4718 | self->dir_path = NULL; | |
4719 | } | |
4720 | self->has_created_dir = true; | |
4721 | clear_cap(_metadata, CAP_DAC_OVERRIDE); | |
4722 | } | |
4723 | ||
4724 | /* Creates file when required. */ | |
4725 | if (stat(variant->file_path, &statbuf)) { | |
4726 | int fd; | |
4727 | ||
4728 | set_cap(_metadata, CAP_DAC_OVERRIDE); | |
4729 | fd = creat(variant->file_path, 0600); | |
4730 | EXPECT_LE(0, fd) | |
4731 | { | |
4732 | TH_LOG("Failed to create file \"%s\": %s", | |
4733 | variant->file_path, strerror(errno)); | |
4734 | } | |
4735 | EXPECT_EQ(0, close(fd)); | |
4736 | self->has_created_file = true; | |
4737 | clear_cap(_metadata, CAP_DAC_OVERRIDE); | |
4738 | } | |
4739 | } | |
4740 | ||
4741 | FIXTURE_TEARDOWN(layout3_fs) | |
4742 | { | |
4743 | if (self->skip_test) | |
4744 | SKIP(return, "this filesystem is not supported (teardown)"); | |
4745 | ||
4746 | if (self->has_created_file) { | |
4747 | set_cap(_metadata, CAP_DAC_OVERRIDE); | |
4748 | /* | |
4749 | * Don't check for error because the file might already | |
4750 | * have been removed (cf. release_inode test). | |
4751 | */ | |
4752 | unlink(variant->file_path); | |
4753 | clear_cap(_metadata, CAP_DAC_OVERRIDE); | |
4754 | } | |
4755 | ||
4756 | if (self->has_created_dir) { | |
4757 | set_cap(_metadata, CAP_DAC_OVERRIDE); | |
4758 | /* | |
4759 | * Don't check for error because the directory might already | |
4760 | * have been removed (cf. release_inode test). | |
4761 | */ | |
4762 | rmdir(self->dir_path); | |
4763 | clear_cap(_metadata, CAP_DAC_OVERRIDE); | |
4764 | } | |
4765 | free(self->dir_path); | |
4766 | self->dir_path = NULL; | |
4767 | ||
4768 | cleanup_layout(_metadata); | |
4769 | } | |
4770 | ||
4771 | static void layer3_fs_tag_inode(struct __test_metadata *const _metadata, | |
4772 | FIXTURE_DATA(layout3_fs) * self, | |
4773 | const FIXTURE_VARIANT(layout3_fs) * variant, | |
4774 | const char *const rule_path) | |
4775 | { | |
4776 | const struct rule layer1_allow_read_file[] = { | |
4777 | { | |
4778 | .path = rule_path, | |
4779 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | |
4780 | }, | |
4781 | {}, | |
4782 | }; | |
4783 | const struct landlock_ruleset_attr layer2_deny_everything_attr = { | |
4784 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, | |
4785 | }; | |
4786 | const char *const dev_null_path = "/dev/null"; | |
4787 | int ruleset_fd; | |
4788 | ||
4789 | if (self->skip_test) | |
4790 | SKIP(return, "this filesystem is not supported (test)"); | |
4791 | ||
4792 | /* Checks without Landlock. */ | |
4793 | EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); | |
4794 | EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); | |
4795 | ||
4796 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | |
4797 | layer1_allow_read_file); | |
4798 | EXPECT_LE(0, ruleset_fd); | |
4799 | enforce_ruleset(_metadata, ruleset_fd); | |
4800 | EXPECT_EQ(0, close(ruleset_fd)); | |
4801 | ||
4802 | EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); | |
4803 | EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); | |
4804 | ||
4805 | /* Forbids directory reading. */ | |
4806 | ruleset_fd = | |
4807 | landlock_create_ruleset(&layer2_deny_everything_attr, | |
4808 | sizeof(layer2_deny_everything_attr), 0); | |
4809 | EXPECT_LE(0, ruleset_fd); | |
4810 | enforce_ruleset(_metadata, ruleset_fd); | |
4811 | EXPECT_EQ(0, close(ruleset_fd)); | |
4812 | ||
4813 | /* Checks with Landlock and forbidden access. */ | |
4814 | EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); | |
4815 | EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); | |
4816 | } | |
4817 | ||
4818 | /* Matrix of tests to check file hierarchy evaluation. */ | |
4819 | ||
4820 | TEST_F_FORK(layout3_fs, tag_inode_dir_parent) | |
4821 | { | |
4822 | /* The current directory must not be the root for this test. */ | |
4823 | layer3_fs_tag_inode(_metadata, self, variant, "."); | |
4824 | } | |
4825 | ||
4826 | TEST_F_FORK(layout3_fs, tag_inode_dir_mnt) | |
4827 | { | |
4828 | layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR); | |
4829 | } | |
4830 | ||
4831 | TEST_F_FORK(layout3_fs, tag_inode_dir_child) | |
4832 | { | |
4833 | layer3_fs_tag_inode(_metadata, self, variant, self->dir_path); | |
4834 | } | |
4835 | ||
4836 | TEST_F_FORK(layout3_fs, tag_inode_file) | |
4837 | { | |
4838 | layer3_fs_tag_inode(_metadata, self, variant, variant->file_path); | |
4839 | } | |
4840 | ||
4841 | /* Light version of layout1.release_inodes */ | |
4842 | TEST_F_FORK(layout3_fs, release_inodes) | |
4843 | { | |
4844 | const struct rule layer1[] = { | |
4845 | { | |
4846 | .path = TMP_DIR, | |
4847 | .access = LANDLOCK_ACCESS_FS_READ_DIR, | |
4848 | }, | |
4849 | {}, | |
4850 | }; | |
4851 | int ruleset_fd; | |
4852 | ||
4853 | if (self->skip_test) | |
4854 | SKIP(return, "this filesystem is not supported (test)"); | |
4855 | ||
4856 | /* Clean up for the teardown to not fail. */ | |
4857 | if (self->has_created_file) | |
4858 | EXPECT_EQ(0, remove_path(variant->file_path)); | |
4859 | ||
4860 | if (self->has_created_dir) | |
4861 | /* Don't check for error because of cgroup specificities. */ | |
4862 | remove_path(self->dir_path); | |
4863 | ||
4864 | ruleset_fd = | |
4865 | create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1); | |
4866 | ASSERT_LE(0, ruleset_fd); | |
4867 | ||
4868 | /* Unmount the filesystem while it is being used by a ruleset. */ | |
4869 | set_cap(_metadata, CAP_SYS_ADMIN); | |
4870 | ASSERT_EQ(0, umount(TMP_DIR)); | |
4871 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
4872 | ||
4873 | /* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */ | |
4874 | set_cap(_metadata, CAP_SYS_ADMIN); | |
4875 | ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR)); | |
4876 | clear_cap(_metadata, CAP_SYS_ADMIN); | |
4877 | ||
4878 | enforce_ruleset(_metadata, ruleset_fd); | |
4879 | ASSERT_EQ(0, close(ruleset_fd)); | |
4880 | ||
4881 | /* Checks that access to the new mount point is denied. */ | |
4882 | ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY)); | |
4883 | } | |
4884 | ||
e1199815 | 4885 | TEST_HARNESS_MAIN |