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
7 * Copyright © 2020-2022 Microsoft Corporation
8 */
9
10 #define _GNU_SOURCE
11 #include <asm/termbits.h>
12 #include <fcntl.h>
13 #include <libgen.h>
14 #include <linux/fiemap.h>
15 #include <linux/landlock.h>
16 #include <linux/magic.h>
17 #include <sched.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <sys/capability.h>
22 #include <sys/ioctl.h>
23 #include <sys/mount.h>
24 #include <sys/prctl.h>
25 #include <sys/sendfile.h>
26 #include <sys/socket.h>
27 #include <sys/stat.h>
28 #include <sys/sysmacros.h>
29 #include <sys/un.h>
30 #include <sys/vfs.h>
31 #include <unistd.h>
32
33 /*
34 * Intentionally included last to work around header conflict.
35 * See https://sourceware.org/glibc/wiki/Synchronizing_Headers.
36 */
37 #include <linux/fs.h>
38 #include <linux/mount.h>
39
40 /* Defines AT_EXECVE_CHECK without type conflicts. */
41 #define _ASM_GENERIC_FCNTL_H
42 #include <linux/fcntl.h>
43
44 #include "audit.h"
45 #include "common.h"
46
47 #ifndef renameat2
renameat2(int olddirfd,const char * oldpath,int newdirfd,const char * newpath,unsigned int flags)48 int renameat2(int olddirfd, const char *oldpath, int newdirfd,
49 const char *newpath, unsigned int flags)
50 {
51 return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath,
52 flags);
53 }
54 #endif
55
56 #ifndef open_tree
open_tree(int dfd,const char * filename,unsigned int flags)57 int open_tree(int dfd, const char *filename, unsigned int flags)
58 {
59 return syscall(__NR_open_tree, dfd, filename, flags);
60 }
61 #endif
62
sys_execveat(int dirfd,const char * pathname,char * const argv[],char * const envp[],int flags)63 static int sys_execveat(int dirfd, const char *pathname, char *const argv[],
64 char *const envp[], int flags)
65 {
66 return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
67 }
68
69 #ifndef RENAME_EXCHANGE
70 #define RENAME_EXCHANGE (1 << 1)
71 #endif
72
73 static const char bin_true[] = "./true";
74
75 /* Paths (sibling number and depth) */
76 static const char dir_s1d1[] = TMP_DIR "/s1d1";
77 static const char file1_s1d1[] = TMP_DIR "/s1d1/f1";
78 static const char file2_s1d1[] = TMP_DIR "/s1d1/f2";
79 static const char dir_s1d2[] = TMP_DIR "/s1d1/s1d2";
80 static const char file1_s1d2[] = TMP_DIR "/s1d1/s1d2/f1";
81 static const char file2_s1d2[] = TMP_DIR "/s1d1/s1d2/f2";
82 static const char dir_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3";
83 static const char file1_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f1";
84 static const char file2_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f2";
85
86 static const char dir_s2d1[] = TMP_DIR "/s2d1";
87 static const char file1_s2d1[] = TMP_DIR "/s2d1/f1";
88 static const char dir_s2d2[] = TMP_DIR "/s2d1/s2d2";
89 static const char file1_s2d2[] = TMP_DIR "/s2d1/s2d2/f1";
90 static const char dir_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3";
91 static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1";
92 static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2";
93
94 static const char dir_s3d1[] = TMP_DIR "/s3d1";
95 static const char file1_s3d1[] = TMP_DIR "/s3d1/f1";
96 /* dir_s3d2 is a mount point. */
97 static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2";
98 static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3";
99 static const char file1_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3/f1";
100 static const char dir_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4";
101 static const char file1_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4/f1";
102
103 /*
104 * layout1 hierarchy:
105 *
106 * tmp
107 * ├── s1d1
108 * │ ├── f1
109 * │ ├── f2
110 * │ └── s1d2
111 * │ ├── f1
112 * │ ├── f2
113 * │ └── s1d3
114 * │ ├── f1
115 * │ └── f2
116 * ├── s2d1
117 * │ ├── f1
118 * │ └── s2d2
119 * │ ├── f1
120 * │ └── s2d3
121 * │ ├── f1
122 * │ └── f2
123 * └── s3d1
124 * ├── f1
125 * └── s3d2 [mount point]
126 * ├── s3d3
127 * │ └── f1
128 * └── s3d4
129 * └── f1
130 */
131
fgrep(FILE * const inf,const char * const str)132 static bool fgrep(FILE *const inf, const char *const str)
133 {
134 char line[32];
135 const int slen = strlen(str);
136
137 while (!feof(inf)) {
138 if (!fgets(line, sizeof(line), inf))
139 break;
140 if (strncmp(line, str, slen))
141 continue;
142
143 return true;
144 }
145
146 return false;
147 }
148
supports_filesystem(const char * const filesystem)149 static bool supports_filesystem(const char *const filesystem)
150 {
151 char str[32];
152 int len;
153 bool res = true;
154 FILE *const inf = fopen("/proc/filesystems", "r");
155
156 /*
157 * Consider that the filesystem is supported if we cannot get the
158 * supported ones.
159 */
160 if (!inf)
161 return true;
162
163 /* filesystem can be null for bind mounts. */
164 if (!filesystem)
165 goto out;
166
167 len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem);
168 if (len >= sizeof(str))
169 /* Ignores too-long filesystem names. */
170 goto out;
171
172 res = fgrep(inf, str);
173
174 out:
175 fclose(inf);
176 return res;
177 }
178
cwd_matches_fs(unsigned int fs_magic)179 static bool cwd_matches_fs(unsigned int fs_magic)
180 {
181 struct statfs statfs_buf;
182
183 if (!fs_magic)
184 return true;
185
186 if (statfs(".", &statfs_buf))
187 return true;
188
189 return statfs_buf.f_type == fs_magic;
190 }
191
mkdir_parents(struct __test_metadata * const _metadata,const char * const path)192 static void mkdir_parents(struct __test_metadata *const _metadata,
193 const char *const path)
194 {
195 char *walker;
196 const char *parent;
197 int i, err;
198
199 ASSERT_NE(path[0], '\0');
200 walker = strdup(path);
201 ASSERT_NE(NULL, walker);
202 parent = walker;
203 for (i = 1; walker[i]; i++) {
204 if (walker[i] != '/')
205 continue;
206 walker[i] = '\0';
207 err = mkdir(parent, 0700);
208 ASSERT_FALSE(err && errno != EEXIST)
209 {
210 TH_LOG("Failed to create directory \"%s\": %s", parent,
211 strerror(errno));
212 }
213 walker[i] = '/';
214 }
215 free(walker);
216 }
217
create_directory(struct __test_metadata * const _metadata,const char * const path)218 static void create_directory(struct __test_metadata *const _metadata,
219 const char *const path)
220 {
221 mkdir_parents(_metadata, path);
222 ASSERT_EQ(0, mkdir(path, 0700))
223 {
224 TH_LOG("Failed to create directory \"%s\": %s", path,
225 strerror(errno));
226 }
227 }
228
create_file(struct __test_metadata * const _metadata,const char * const path)229 static void create_file(struct __test_metadata *const _metadata,
230 const char *const path)
231 {
232 mkdir_parents(_metadata, path);
233 ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0))
234 {
235 TH_LOG("Failed to create file \"%s\": %s", path,
236 strerror(errno));
237 }
238 }
239
remove_path(const char * const path)240 static int remove_path(const char *const path)
241 {
242 char *walker;
243 int i, ret, err = 0;
244
245 walker = strdup(path);
246 if (!walker) {
247 err = ENOMEM;
248 goto out;
249 }
250 if (unlink(path) && rmdir(path)) {
251 if (errno != ENOENT && errno != ENOTDIR)
252 err = errno;
253 goto out;
254 }
255 for (i = strlen(walker); i > 0; i--) {
256 if (walker[i] != '/')
257 continue;
258 walker[i] = '\0';
259 ret = rmdir(walker);
260 if (ret) {
261 if (errno != ENOTEMPTY && errno != EBUSY)
262 err = errno;
263 goto out;
264 }
265 if (strcmp(walker, TMP_DIR) == 0)
266 goto out;
267 }
268
269 out:
270 free(walker);
271 return err;
272 }
273
274 struct mnt_opt {
275 const char *const source;
276 const char *const type;
277 const unsigned long flags;
278 const char *const data;
279 };
280
281 #define MNT_TMP_DATA "size=4m,mode=700"
282
283 static const struct mnt_opt mnt_tmp = {
284 .type = "tmpfs",
285 .data = MNT_TMP_DATA,
286 };
287
mount_opt(const struct mnt_opt * const mnt,const char * const target)288 static int mount_opt(const struct mnt_opt *const mnt, const char *const target)
289 {
290 return mount(mnt->source ?: mnt->type, target, mnt->type, mnt->flags,
291 mnt->data);
292 }
293
prepare_layout_opt(struct __test_metadata * const _metadata,const struct mnt_opt * const mnt)294 static void prepare_layout_opt(struct __test_metadata *const _metadata,
295 const struct mnt_opt *const mnt)
296 {
297 disable_caps(_metadata);
298 umask(0077);
299 create_directory(_metadata, TMP_DIR);
300
301 /*
302 * Do not pollute the rest of the system: creates a private mount point
303 * for tests relying on pivot_root(2) and move_mount(2).
304 */
305 set_cap(_metadata, CAP_SYS_ADMIN);
306 ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWCGROUP));
307 ASSERT_EQ(0, mount_opt(mnt, TMP_DIR))
308 {
309 TH_LOG("Failed to mount the %s filesystem: %s", mnt->type,
310 strerror(errno));
311 /*
312 * FIXTURE_TEARDOWN() is not called when FIXTURE_SETUP()
313 * failed, so we need to explicitly do a minimal cleanup to
314 * avoid cascading errors with other tests that don't depend on
315 * the same filesystem.
316 */
317 remove_path(TMP_DIR);
318 }
319 ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL));
320 clear_cap(_metadata, CAP_SYS_ADMIN);
321 }
322
prepare_layout(struct __test_metadata * const _metadata)323 static void prepare_layout(struct __test_metadata *const _metadata)
324 {
325 prepare_layout_opt(_metadata, &mnt_tmp);
326 }
327
cleanup_layout(struct __test_metadata * const _metadata)328 static void cleanup_layout(struct __test_metadata *const _metadata)
329 {
330 set_cap(_metadata, CAP_SYS_ADMIN);
331 if (umount(TMP_DIR)) {
332 /*
333 * According to the test environment, the mount point of the
334 * current directory may be shared or not, which changes the
335 * visibility of the nested TMP_DIR mount point for the test's
336 * parent process doing this cleanup.
337 */
338 ASSERT_EQ(EINVAL, errno);
339 }
340 clear_cap(_metadata, CAP_SYS_ADMIN);
341 EXPECT_EQ(0, remove_path(TMP_DIR));
342 }
343
344 /* clang-format off */
FIXTURE(layout0)345 FIXTURE(layout0) {};
346 /* clang-format on */
347
FIXTURE_SETUP(layout0)348 FIXTURE_SETUP(layout0)
349 {
350 prepare_layout(_metadata);
351 }
352
FIXTURE_TEARDOWN_PARENT(layout0)353 FIXTURE_TEARDOWN_PARENT(layout0)
354 {
355 cleanup_layout(_metadata);
356 }
357
create_layout1(struct __test_metadata * const _metadata)358 static void create_layout1(struct __test_metadata *const _metadata)
359 {
360 create_file(_metadata, file1_s1d1);
361 create_file(_metadata, file1_s1d2);
362 create_file(_metadata, file1_s1d3);
363 create_file(_metadata, file2_s1d1);
364 create_file(_metadata, file2_s1d2);
365 create_file(_metadata, file2_s1d3);
366
367 create_file(_metadata, file1_s2d1);
368 create_file(_metadata, file1_s2d2);
369 create_file(_metadata, file1_s2d3);
370 create_file(_metadata, file2_s2d3);
371
372 create_file(_metadata, file1_s3d1);
373 create_directory(_metadata, dir_s3d2);
374 set_cap(_metadata, CAP_SYS_ADMIN);
375 ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
376 clear_cap(_metadata, CAP_SYS_ADMIN);
377
378 create_file(_metadata, file1_s3d3);
379 create_file(_metadata, file1_s3d4);
380 }
381
remove_layout1(struct __test_metadata * const _metadata)382 static void remove_layout1(struct __test_metadata *const _metadata)
383 {
384 EXPECT_EQ(0, remove_path(file2_s1d3));
385 EXPECT_EQ(0, remove_path(file2_s1d2));
386 EXPECT_EQ(0, remove_path(file2_s1d1));
387 EXPECT_EQ(0, remove_path(file1_s1d3));
388 EXPECT_EQ(0, remove_path(file1_s1d2));
389 EXPECT_EQ(0, remove_path(file1_s1d1));
390 EXPECT_EQ(0, remove_path(dir_s1d3));
391
392 EXPECT_EQ(0, remove_path(file2_s2d3));
393 EXPECT_EQ(0, remove_path(file1_s2d3));
394 EXPECT_EQ(0, remove_path(file1_s2d2));
395 EXPECT_EQ(0, remove_path(file1_s2d1));
396 EXPECT_EQ(0, remove_path(dir_s2d2));
397
398 EXPECT_EQ(0, remove_path(file1_s3d1));
399 EXPECT_EQ(0, remove_path(file1_s3d3));
400 EXPECT_EQ(0, remove_path(file1_s3d4));
401 set_cap(_metadata, CAP_SYS_ADMIN);
402 umount(dir_s3d2);
403 clear_cap(_metadata, CAP_SYS_ADMIN);
404 EXPECT_EQ(0, remove_path(dir_s3d2));
405 }
406
407 /* clang-format off */
FIXTURE(layout1)408 FIXTURE(layout1) {};
409 /* clang-format on */
410
FIXTURE_SETUP(layout1)411 FIXTURE_SETUP(layout1)
412 {
413 prepare_layout(_metadata);
414
415 create_layout1(_metadata);
416 }
417
FIXTURE_TEARDOWN_PARENT(layout1)418 FIXTURE_TEARDOWN_PARENT(layout1)
419 {
420 remove_layout1(_metadata);
421
422 cleanup_layout(_metadata);
423 }
424
425 /*
426 * This helper enables to use the ASSERT_* macros and print the line number
427 * pointing to the test caller.
428 */
test_open_rel(const int dirfd,const char * const path,const int flags)429 static int test_open_rel(const int dirfd, const char *const path,
430 const int flags)
431 {
432 int fd;
433
434 /* Works with file and directories. */
435 fd = openat(dirfd, path, flags | O_CLOEXEC);
436 if (fd < 0)
437 return errno;
438 /*
439 * Mixing error codes from close(2) and open(2) should not lead to any
440 * (access type) confusion for this test.
441 */
442 if (close(fd) != 0)
443 return errno;
444 return 0;
445 }
446
test_open(const char * const path,const int flags)447 static int test_open(const char *const path, const int flags)
448 {
449 return test_open_rel(AT_FDCWD, path, flags);
450 }
451
TEST_F_FORK(layout1,no_restriction)452 TEST_F_FORK(layout1, no_restriction)
453 {
454 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
455 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
456 ASSERT_EQ(0, test_open(file2_s1d1, O_RDONLY));
457 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
458 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
459 ASSERT_EQ(0, test_open(file2_s1d2, O_RDONLY));
460 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY));
461 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
462
463 ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY));
464 ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY));
465 ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY));
466 ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY));
467 ASSERT_EQ(0, test_open(dir_s2d3, O_RDONLY));
468 ASSERT_EQ(0, test_open(file1_s2d3, O_RDONLY));
469
470 ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY));
471 ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));
472 ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY));
473 }
474
TEST_F_FORK(layout1,inval)475 TEST_F_FORK(layout1, inval)
476 {
477 struct landlock_path_beneath_attr path_beneath = {
478 .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
479 LANDLOCK_ACCESS_FS_WRITE_FILE,
480 .parent_fd = -1,
481 };
482 struct landlock_ruleset_attr ruleset_attr = {
483 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
484 LANDLOCK_ACCESS_FS_WRITE_FILE,
485 };
486 int ruleset_fd;
487
488 path_beneath.parent_fd =
489 open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC);
490 ASSERT_LE(0, path_beneath.parent_fd);
491
492 ruleset_fd = open(dir_s1d1, O_PATH | O_DIRECTORY | O_CLOEXEC);
493 ASSERT_LE(0, ruleset_fd);
494 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
495 &path_beneath, 0));
496 /* Returns EBADF because ruleset_fd is not a landlock-ruleset FD. */
497 ASSERT_EQ(EBADF, errno);
498 ASSERT_EQ(0, close(ruleset_fd));
499
500 ruleset_fd = open(dir_s1d1, O_DIRECTORY | O_CLOEXEC);
501 ASSERT_LE(0, ruleset_fd);
502 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
503 &path_beneath, 0));
504 /* Returns EBADFD because ruleset_fd is not a valid ruleset. */
505 ASSERT_EQ(EBADFD, errno);
506 ASSERT_EQ(0, close(ruleset_fd));
507
508 /* Gets a real ruleset. */
509 ruleset_fd =
510 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
511 ASSERT_LE(0, ruleset_fd);
512 ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
513 &path_beneath, 0));
514 ASSERT_EQ(0, close(path_beneath.parent_fd));
515
516 /* Tests without O_PATH. */
517 path_beneath.parent_fd = open(dir_s1d2, O_DIRECTORY | O_CLOEXEC);
518 ASSERT_LE(0, path_beneath.parent_fd);
519 ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
520 &path_beneath, 0));
521 ASSERT_EQ(0, close(path_beneath.parent_fd));
522
523 /* Tests with a ruleset FD. */
524 path_beneath.parent_fd = ruleset_fd;
525 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
526 &path_beneath, 0));
527 ASSERT_EQ(EBADFD, errno);
528
529 /* Checks unhandled allowed_access. */
530 path_beneath.parent_fd =
531 open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC);
532 ASSERT_LE(0, path_beneath.parent_fd);
533
534 /* Test with legitimate values. */
535 path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE;
536 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
537 &path_beneath, 0));
538 ASSERT_EQ(EINVAL, errno);
539 path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE;
540
541 /* Tests with denied-by-default access right. */
542 path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER;
543 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
544 &path_beneath, 0));
545 ASSERT_EQ(EINVAL, errno);
546 path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_REFER;
547
548 /* Test with unknown (64-bits) value. */
549 path_beneath.allowed_access |= (1ULL << 60);
550 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
551 &path_beneath, 0));
552 ASSERT_EQ(EINVAL, errno);
553 path_beneath.allowed_access &= ~(1ULL << 60);
554
555 /* Test with no access. */
556 path_beneath.allowed_access = 0;
557 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
558 &path_beneath, 0));
559 ASSERT_EQ(ENOMSG, errno);
560 path_beneath.allowed_access &= ~(1ULL << 60);
561
562 ASSERT_EQ(0, close(path_beneath.parent_fd));
563
564 /* Enforces the ruleset. */
565 ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
566 ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
567
568 ASSERT_EQ(0, close(ruleset_fd));
569 }
570
571 /* clang-format off */
572
573 #define ACCESS_FILE ( \
574 LANDLOCK_ACCESS_FS_EXECUTE | \
575 LANDLOCK_ACCESS_FS_WRITE_FILE | \
576 LANDLOCK_ACCESS_FS_READ_FILE | \
577 LANDLOCK_ACCESS_FS_TRUNCATE | \
578 LANDLOCK_ACCESS_FS_IOCTL_DEV)
579
580 #define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
581
582 #define ACCESS_ALL ( \
583 ACCESS_FILE | \
584 LANDLOCK_ACCESS_FS_READ_DIR | \
585 LANDLOCK_ACCESS_FS_REMOVE_DIR | \
586 LANDLOCK_ACCESS_FS_REMOVE_FILE | \
587 LANDLOCK_ACCESS_FS_MAKE_CHAR | \
588 LANDLOCK_ACCESS_FS_MAKE_DIR | \
589 LANDLOCK_ACCESS_FS_MAKE_REG | \
590 LANDLOCK_ACCESS_FS_MAKE_SOCK | \
591 LANDLOCK_ACCESS_FS_MAKE_FIFO | \
592 LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
593 LANDLOCK_ACCESS_FS_MAKE_SYM | \
594 LANDLOCK_ACCESS_FS_REFER)
595
596 /* clang-format on */
597
TEST_F_FORK(layout1,file_and_dir_access_rights)598 TEST_F_FORK(layout1, file_and_dir_access_rights)
599 {
600 __u64 access;
601 int err;
602 struct landlock_path_beneath_attr path_beneath_file = {},
603 path_beneath_dir = {};
604 struct landlock_ruleset_attr ruleset_attr = {
605 .handled_access_fs = ACCESS_ALL,
606 };
607 const int ruleset_fd =
608 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
609
610 ASSERT_LE(0, ruleset_fd);
611
612 /* Tests access rights for files. */
613 path_beneath_file.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC);
614 ASSERT_LE(0, path_beneath_file.parent_fd);
615
616 /* Tests access rights for directories. */
617 path_beneath_dir.parent_fd =
618 open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC);
619 ASSERT_LE(0, path_beneath_dir.parent_fd);
620
621 for (access = 1; access <= ACCESS_LAST; access <<= 1) {
622 path_beneath_dir.allowed_access = access;
623 ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
624 LANDLOCK_RULE_PATH_BENEATH,
625 &path_beneath_dir, 0));
626
627 path_beneath_file.allowed_access = access;
628 err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
629 &path_beneath_file, 0);
630 if (access & ACCESS_FILE) {
631 ASSERT_EQ(0, err);
632 } else {
633 ASSERT_EQ(-1, err);
634 ASSERT_EQ(EINVAL, errno);
635 }
636 }
637 ASSERT_EQ(0, close(path_beneath_file.parent_fd));
638 ASSERT_EQ(0, close(path_beneath_dir.parent_fd));
639 ASSERT_EQ(0, close(ruleset_fd));
640 }
641
TEST_F_FORK(layout0,ruleset_with_unknown_access)642 TEST_F_FORK(layout0, ruleset_with_unknown_access)
643 {
644 __u64 access_mask;
645
646 for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST;
647 access_mask >>= 1) {
648 struct landlock_ruleset_attr ruleset_attr = {
649 .handled_access_fs = access_mask,
650 };
651
652 ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
653 sizeof(ruleset_attr), 0));
654 ASSERT_EQ(EINVAL, errno);
655 }
656 }
657
TEST_F_FORK(layout0,rule_with_unknown_access)658 TEST_F_FORK(layout0, rule_with_unknown_access)
659 {
660 __u64 access;
661 struct landlock_path_beneath_attr path_beneath = {};
662 const struct landlock_ruleset_attr ruleset_attr = {
663 .handled_access_fs = ACCESS_ALL,
664 };
665 const int ruleset_fd =
666 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
667
668 ASSERT_LE(0, ruleset_fd);
669
670 path_beneath.parent_fd =
671 open(TMP_DIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
672 ASSERT_LE(0, path_beneath.parent_fd);
673
674 for (access = 1ULL << 63; access != ACCESS_LAST; access >>= 1) {
675 path_beneath.allowed_access = access;
676 EXPECT_EQ(-1, landlock_add_rule(ruleset_fd,
677 LANDLOCK_RULE_PATH_BENEATH,
678 &path_beneath, 0));
679 EXPECT_EQ(EINVAL, errno);
680 }
681 ASSERT_EQ(0, close(path_beneath.parent_fd));
682 ASSERT_EQ(0, close(ruleset_fd));
683 }
684
TEST_F_FORK(layout1,rule_with_unhandled_access)685 TEST_F_FORK(layout1, rule_with_unhandled_access)
686 {
687 struct landlock_ruleset_attr ruleset_attr = {
688 .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
689 };
690 struct landlock_path_beneath_attr path_beneath = {};
691 int ruleset_fd;
692 __u64 access;
693
694 ruleset_fd =
695 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
696 ASSERT_LE(0, ruleset_fd);
697
698 path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC);
699 ASSERT_LE(0, path_beneath.parent_fd);
700
701 for (access = 1; access > 0; access <<= 1) {
702 int err;
703
704 path_beneath.allowed_access = access;
705 err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
706 &path_beneath, 0);
707 if (access == ruleset_attr.handled_access_fs) {
708 EXPECT_EQ(0, err);
709 } else {
710 EXPECT_EQ(-1, err);
711 EXPECT_EQ(EINVAL, errno);
712 }
713 }
714
715 EXPECT_EQ(0, close(path_beneath.parent_fd));
716 EXPECT_EQ(0, close(ruleset_fd));
717 }
718
add_path_beneath(struct __test_metadata * const _metadata,const int ruleset_fd,const __u64 allowed_access,const char * const path)719 static void add_path_beneath(struct __test_metadata *const _metadata,
720 const int ruleset_fd, const __u64 allowed_access,
721 const char *const path)
722 {
723 struct landlock_path_beneath_attr path_beneath = {
724 .allowed_access = allowed_access,
725 };
726
727 path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC);
728 ASSERT_LE(0, path_beneath.parent_fd)
729 {
730 TH_LOG("Failed to open directory \"%s\": %s", path,
731 strerror(errno));
732 }
733 ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
734 &path_beneath, 0))
735 {
736 TH_LOG("Failed to update the ruleset with \"%s\": %s", path,
737 strerror(errno));
738 }
739 ASSERT_EQ(0, close(path_beneath.parent_fd));
740 }
741
742 struct rule {
743 const char *path;
744 __u64 access;
745 };
746
747 /* clang-format off */
748
749 #define ACCESS_RO ( \
750 LANDLOCK_ACCESS_FS_READ_FILE | \
751 LANDLOCK_ACCESS_FS_READ_DIR)
752
753 #define ACCESS_RW ( \
754 ACCESS_RO | \
755 LANDLOCK_ACCESS_FS_WRITE_FILE)
756
757 /* clang-format on */
758
create_ruleset(struct __test_metadata * const _metadata,const __u64 handled_access_fs,const struct rule rules[])759 static int create_ruleset(struct __test_metadata *const _metadata,
760 const __u64 handled_access_fs,
761 const struct rule rules[])
762 {
763 int ruleset_fd, i;
764 struct landlock_ruleset_attr ruleset_attr = {
765 .handled_access_fs = handled_access_fs,
766 };
767
768 ASSERT_NE(NULL, rules)
769 {
770 TH_LOG("No rule list");
771 }
772 ASSERT_NE(NULL, rules[0].path)
773 {
774 TH_LOG("Empty rule list");
775 }
776
777 ruleset_fd =
778 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
779 ASSERT_LE(0, ruleset_fd)
780 {
781 TH_LOG("Failed to create a ruleset: %s", strerror(errno));
782 }
783
784 for (i = 0; rules[i].path; i++) {
785 if (!rules[i].access)
786 continue;
787
788 add_path_beneath(_metadata, ruleset_fd, rules[i].access,
789 rules[i].path);
790 }
791 return ruleset_fd;
792 }
793
TEST_F_FORK(layout0,proc_nsfs)794 TEST_F_FORK(layout0, proc_nsfs)
795 {
796 const struct rule rules[] = {
797 {
798 .path = "/dev/null",
799 .access = LANDLOCK_ACCESS_FS_READ_FILE |
800 LANDLOCK_ACCESS_FS_WRITE_FILE,
801 },
802 {},
803 };
804 struct landlock_path_beneath_attr path_beneath;
805 const int ruleset_fd = create_ruleset(
806 _metadata, rules[0].access | LANDLOCK_ACCESS_FS_READ_DIR,
807 rules);
808
809 ASSERT_LE(0, ruleset_fd);
810 ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY));
811
812 enforce_ruleset(_metadata, ruleset_fd);
813
814 ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
815 ASSERT_EQ(EACCES, test_open("/dev", O_RDONLY));
816 ASSERT_EQ(0, test_open("/dev/null", O_RDONLY));
817 ASSERT_EQ(EACCES, test_open("/dev/full", O_RDONLY));
818
819 ASSERT_EQ(EACCES, test_open("/proc", O_RDONLY));
820 ASSERT_EQ(EACCES, test_open("/proc/self", O_RDONLY));
821 ASSERT_EQ(EACCES, test_open("/proc/self/ns", O_RDONLY));
822 /*
823 * Because nsfs is an internal filesystem, /proc/self/ns/mnt is a
824 * disconnected path. Such path cannot be identified and must then be
825 * allowed.
826 */
827 ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY));
828
829 /*
830 * Checks that it is not possible to add nsfs-like filesystem
831 * references to a ruleset.
832 */
833 path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
834 LANDLOCK_ACCESS_FS_WRITE_FILE,
835 path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC);
836 ASSERT_LE(0, path_beneath.parent_fd);
837 ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
838 &path_beneath, 0));
839 ASSERT_EQ(EBADFD, errno);
840 ASSERT_EQ(0, close(path_beneath.parent_fd));
841 }
842
TEST_F_FORK(layout0,unpriv)843 TEST_F_FORK(layout0, unpriv)
844 {
845 const struct rule rules[] = {
846 {
847 .path = TMP_DIR,
848 .access = ACCESS_RO,
849 },
850 {},
851 };
852 int ruleset_fd;
853
854 drop_caps(_metadata);
855
856 ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules);
857 ASSERT_LE(0, ruleset_fd);
858 ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0));
859 ASSERT_EQ(EPERM, errno);
860
861 /* enforce_ruleset() calls prctl(no_new_privs). */
862 enforce_ruleset(_metadata, ruleset_fd);
863 ASSERT_EQ(0, close(ruleset_fd));
864 }
865
TEST_F_FORK(layout1,effective_access)866 TEST_F_FORK(layout1, effective_access)
867 {
868 const struct rule rules[] = {
869 {
870 .path = dir_s1d2,
871 .access = ACCESS_RO,
872 },
873 {
874 .path = file1_s2d2,
875 .access = LANDLOCK_ACCESS_FS_READ_FILE |
876 LANDLOCK_ACCESS_FS_WRITE_FILE,
877 },
878 {},
879 };
880 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
881 char buf;
882 int reg_fd;
883
884 ASSERT_LE(0, ruleset_fd);
885 enforce_ruleset(_metadata, ruleset_fd);
886 ASSERT_EQ(0, close(ruleset_fd));
887
888 /* Tests on a directory (with or without O_PATH). */
889 ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
890 ASSERT_EQ(0, test_open("/", O_RDONLY | O_PATH));
891 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
892 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_PATH));
893 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
894 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY | O_PATH));
895
896 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
897 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
898 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY));
899 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
900
901 /* Tests on a file (with or without O_PATH). */
902 ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY));
903 ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_PATH));
904
905 ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY));
906
907 /* Checks effective read and write actions. */
908 reg_fd = open(file1_s2d2, O_RDWR | O_CLOEXEC);
909 ASSERT_LE(0, reg_fd);
910 ASSERT_EQ(1, write(reg_fd, ".", 1));
911 ASSERT_LE(0, lseek(reg_fd, 0, SEEK_SET));
912 ASSERT_EQ(1, read(reg_fd, &buf, 1));
913 ASSERT_EQ('.', buf);
914 ASSERT_EQ(0, close(reg_fd));
915
916 /* Just in case, double-checks effective actions. */
917 reg_fd = open(file1_s2d2, O_RDONLY | O_CLOEXEC);
918 ASSERT_LE(0, reg_fd);
919 ASSERT_EQ(-1, write(reg_fd, &buf, 1));
920 ASSERT_EQ(EBADF, errno);
921 ASSERT_EQ(0, close(reg_fd));
922 }
923
TEST_F_FORK(layout1,unhandled_access)924 TEST_F_FORK(layout1, unhandled_access)
925 {
926 const struct rule rules[] = {
927 {
928 .path = dir_s1d2,
929 .access = ACCESS_RO,
930 },
931 {},
932 };
933 /* Here, we only handle read accesses, not write accesses. */
934 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules);
935
936 ASSERT_LE(0, ruleset_fd);
937 enforce_ruleset(_metadata, ruleset_fd);
938 ASSERT_EQ(0, close(ruleset_fd));
939
940 /*
941 * Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE,
942 * opening for write-only should be allowed, but not read-write.
943 */
944 ASSERT_EQ(0, test_open(file1_s1d1, O_WRONLY));
945 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
946
947 ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY));
948 ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR));
949 }
950
TEST_F_FORK(layout1,ruleset_overlap)951 TEST_F_FORK(layout1, ruleset_overlap)
952 {
953 const struct rule rules[] = {
954 /* These rules should be ORed among them. */
955 {
956 .path = dir_s1d2,
957 .access = LANDLOCK_ACCESS_FS_READ_FILE |
958 LANDLOCK_ACCESS_FS_WRITE_FILE,
959 },
960 {
961 .path = dir_s1d2,
962 .access = LANDLOCK_ACCESS_FS_READ_FILE |
963 LANDLOCK_ACCESS_FS_READ_DIR,
964 },
965 {},
966 };
967 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
968
969 ASSERT_LE(0, ruleset_fd);
970 enforce_ruleset(_metadata, ruleset_fd);
971 ASSERT_EQ(0, close(ruleset_fd));
972
973 /* Checks s1d1 hierarchy. */
974 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
975 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
976 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
977 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
978
979 /* Checks s1d2 hierarchy. */
980 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
981 ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY));
982 ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR));
983 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
984
985 /* Checks s1d3 hierarchy. */
986 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
987 ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
988 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
989 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
990 }
991
TEST_F_FORK(layout1,layer_rule_unions)992 TEST_F_FORK(layout1, layer_rule_unions)
993 {
994 const struct rule layer1[] = {
995 {
996 .path = dir_s1d2,
997 .access = LANDLOCK_ACCESS_FS_READ_FILE,
998 },
999 /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
1000 {
1001 .path = dir_s1d3,
1002 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
1003 },
1004 {},
1005 };
1006 const struct rule layer2[] = {
1007 /* Doesn't change anything from layer1. */
1008 {
1009 .path = dir_s1d2,
1010 .access = LANDLOCK_ACCESS_FS_READ_FILE |
1011 LANDLOCK_ACCESS_FS_WRITE_FILE,
1012 },
1013 {},
1014 };
1015 const struct rule layer3[] = {
1016 /* Only allows write (but not read) to dir_s1d3. */
1017 {
1018 .path = dir_s1d2,
1019 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
1020 },
1021 {},
1022 };
1023 int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1);
1024
1025 ASSERT_LE(0, ruleset_fd);
1026 enforce_ruleset(_metadata, ruleset_fd);
1027 ASSERT_EQ(0, close(ruleset_fd));
1028
1029 /* Checks s1d1 hierarchy with layer1. */
1030 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
1031 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1032 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
1033 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1034
1035 /* Checks s1d2 hierarchy with layer1. */
1036 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
1037 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1038 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
1039 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1040
1041 /* Checks s1d3 hierarchy with layer1. */
1042 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1043 ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
1044 /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
1045 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
1046 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1047
1048 /* Doesn't change anything from layer1. */
1049 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2);
1050 ASSERT_LE(0, ruleset_fd);
1051 enforce_ruleset(_metadata, ruleset_fd);
1052 ASSERT_EQ(0, close(ruleset_fd));
1053
1054 /* Checks s1d1 hierarchy with layer2. */
1055 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
1056 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1057 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
1058 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1059
1060 /* Checks s1d2 hierarchy with layer2. */
1061 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
1062 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1063 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
1064 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1065
1066 /* Checks s1d3 hierarchy with layer2. */
1067 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1068 ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
1069 /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
1070 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
1071 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1072
1073 /* Only allows write (but not read) to dir_s1d3. */
1074 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3);
1075 ASSERT_LE(0, ruleset_fd);
1076 enforce_ruleset(_metadata, ruleset_fd);
1077 ASSERT_EQ(0, close(ruleset_fd));
1078
1079 /* Checks s1d1 hierarchy with layer3. */
1080 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
1081 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1082 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
1083 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1084
1085 /* Checks s1d2 hierarchy with layer3. */
1086 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY));
1087 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1088 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
1089 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1090
1091 /* Checks s1d3 hierarchy with layer3. */
1092 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
1093 ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
1094 /* dir_s1d3 should now deny READ_FILE and WRITE_FILE (O_RDWR). */
1095 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDWR));
1096 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1097 }
1098
TEST_F_FORK(layout1,non_overlapping_accesses)1099 TEST_F_FORK(layout1, non_overlapping_accesses)
1100 {
1101 const struct rule layer1[] = {
1102 {
1103 .path = dir_s1d2,
1104 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
1105 },
1106 {},
1107 };
1108 const struct rule layer2[] = {
1109 {
1110 .path = dir_s1d3,
1111 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
1112 },
1113 {},
1114 };
1115 int ruleset_fd;
1116
1117 ASSERT_EQ(0, unlink(file1_s1d1));
1118 ASSERT_EQ(0, unlink(file1_s1d2));
1119
1120 ruleset_fd =
1121 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1);
1122 ASSERT_LE(0, ruleset_fd);
1123 enforce_ruleset(_metadata, ruleset_fd);
1124 ASSERT_EQ(0, close(ruleset_fd));
1125
1126 ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0));
1127 ASSERT_EQ(EACCES, errno);
1128 ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0));
1129 ASSERT_EQ(0, unlink(file1_s1d2));
1130
1131 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE,
1132 layer2);
1133 ASSERT_LE(0, ruleset_fd);
1134 enforce_ruleset(_metadata, ruleset_fd);
1135 ASSERT_EQ(0, close(ruleset_fd));
1136
1137 /* Unchanged accesses for file creation. */
1138 ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0));
1139 ASSERT_EQ(EACCES, errno);
1140 ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0));
1141
1142 /* Checks file removing. */
1143 ASSERT_EQ(-1, unlink(file1_s1d2));
1144 ASSERT_EQ(EACCES, errno);
1145 ASSERT_EQ(0, unlink(file1_s1d3));
1146 }
1147
TEST_F_FORK(layout1,interleaved_masked_accesses)1148 TEST_F_FORK(layout1, interleaved_masked_accesses)
1149 {
1150 /*
1151 * Checks overly restrictive rules:
1152 * layer 1: allows R s1d1/s1d2/s1d3/file1
1153 * layer 2: allows RW s1d1/s1d2/s1d3
1154 * allows W s1d1/s1d2
1155 * denies R s1d1/s1d2
1156 * layer 3: allows R s1d1
1157 * layer 4: allows R s1d1/s1d2
1158 * denies W s1d1/s1d2
1159 * layer 5: allows R s1d1/s1d2
1160 * layer 6: allows X ----
1161 * layer 7: allows W s1d1/s1d2
1162 * denies R s1d1/s1d2
1163 */
1164 const struct rule layer1_read[] = {
1165 /* Allows read access to file1_s1d3 with the first layer. */
1166 {
1167 .path = file1_s1d3,
1168 .access = LANDLOCK_ACCESS_FS_READ_FILE,
1169 },
1170 {},
1171 };
1172 /* First rule with write restrictions. */
1173 const struct rule layer2_read_write[] = {
1174 /* Start by granting read-write access via its parent directory... */
1175 {
1176 .path = dir_s1d3,
1177 .access = LANDLOCK_ACCESS_FS_READ_FILE |
1178 LANDLOCK_ACCESS_FS_WRITE_FILE,
1179 },
1180 /* ...but also denies read access via its grandparent directory. */
1181 {
1182 .path = dir_s1d2,
1183 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
1184 },
1185 {},
1186 };
1187 const struct rule layer3_read[] = {
1188 /* Allows read access via its great-grandparent directory. */
1189 {
1190 .path = dir_s1d1,
1191 .access = LANDLOCK_ACCESS_FS_READ_FILE,
1192 },
1193 {},
1194 };
1195 const struct rule layer4_read_write[] = {
1196 /*
1197 * Try to confuse the deny access by denying write (but not
1198 * read) access via its grandparent directory.
1199 */
1200 {
1201 .path = dir_s1d2,
1202 .access = LANDLOCK_ACCESS_FS_READ_FILE,
1203 },
1204 {},
1205 };
1206 const struct rule layer5_read[] = {
1207 /*
1208 * Try to override layer2's deny read access by explicitly
1209 * allowing read access via file1_s1d3's grandparent.
1210 */
1211 {
1212 .path = dir_s1d2,
1213 .access = LANDLOCK_ACCESS_FS_READ_FILE,
1214 },
1215 {},
1216 };
1217 const struct rule layer6_execute[] = {
1218 /*
1219 * Restricts an unrelated file hierarchy with a new access
1220 * (non-overlapping) type.
1221 */
1222 {
1223 .path = dir_s2d1,
1224 .access = LANDLOCK_ACCESS_FS_EXECUTE,
1225 },
1226 {},
1227 };
1228 const struct rule layer7_read_write[] = {
1229 /*
1230 * Finally, denies read access to file1_s1d3 via its
1231 * grandparent.
1232 */
1233 {
1234 .path = dir_s1d2,
1235 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
1236 },
1237 {},
1238 };
1239 int ruleset_fd;
1240
1241 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
1242 layer1_read);
1243 ASSERT_LE(0, ruleset_fd);
1244 enforce_ruleset(_metadata, ruleset_fd);
1245 ASSERT_EQ(0, close(ruleset_fd));
1246
1247 /* Checks that read access is granted for file1_s1d3 with layer 1. */
1248 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
1249 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1250 ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
1251
1252 ruleset_fd = create_ruleset(_metadata,
1253 LANDLOCK_ACCESS_FS_READ_FILE |
1254 LANDLOCK_ACCESS_FS_WRITE_FILE,
1255 layer2_read_write);
1256 ASSERT_LE(0, ruleset_fd);
1257 enforce_ruleset(_metadata, ruleset_fd);
1258 ASSERT_EQ(0, close(ruleset_fd));
1259
1260 /* Checks that previous access rights are unchanged with layer 2. */
1261 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
1262 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1263 ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
1264
1265 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
1266 layer3_read);
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 3. */
1272 ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
1273 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1274 ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
1275
1276 /* This time, denies write access for the file hierarchy. */
1277 ruleset_fd = create_ruleset(_metadata,
1278 LANDLOCK_ACCESS_FS_READ_FILE |
1279 LANDLOCK_ACCESS_FS_WRITE_FILE,
1280 layer4_read_write);
1281 ASSERT_LE(0, ruleset_fd);
1282 enforce_ruleset(_metadata, ruleset_fd);
1283 ASSERT_EQ(0, close(ruleset_fd));
1284
1285 /*
1286 * Checks that the only change with layer 4 is that write access is
1287 * denied.
1288 */
1289 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1290 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1291 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1292 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
1293
1294 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
1295 layer5_read);
1296 ASSERT_LE(0, ruleset_fd);
1297 enforce_ruleset(_metadata, ruleset_fd);
1298 ASSERT_EQ(0, close(ruleset_fd));
1299
1300 /* Checks that previous access rights are unchanged with layer 5. */
1301 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1302 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1303 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
1304 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1305
1306 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE,
1307 layer6_execute);
1308 ASSERT_LE(0, ruleset_fd);
1309 enforce_ruleset(_metadata, ruleset_fd);
1310 ASSERT_EQ(0, close(ruleset_fd));
1311
1312 /* Checks that previous access rights are unchanged with layer 6. */
1313 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1314 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1315 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
1316 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1317
1318 ruleset_fd = create_ruleset(_metadata,
1319 LANDLOCK_ACCESS_FS_READ_FILE |
1320 LANDLOCK_ACCESS_FS_WRITE_FILE,
1321 layer7_read_write);
1322 ASSERT_LE(0, ruleset_fd);
1323 enforce_ruleset(_metadata, ruleset_fd);
1324 ASSERT_EQ(0, close(ruleset_fd));
1325
1326 /* Checks read access is now denied with layer 7. */
1327 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
1328 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1329 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
1330 ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
1331 }
1332
TEST_F_FORK(layout1,inherit_subset)1333 TEST_F_FORK(layout1, inherit_subset)
1334 {
1335 const struct rule rules[] = {
1336 {
1337 .path = dir_s1d2,
1338 .access = LANDLOCK_ACCESS_FS_READ_FILE |
1339 LANDLOCK_ACCESS_FS_READ_DIR,
1340 },
1341 {},
1342 };
1343 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1344
1345 ASSERT_LE(0, ruleset_fd);
1346 enforce_ruleset(_metadata, ruleset_fd);
1347
1348 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1349 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1350
1351 /* Write access is forbidden. */
1352 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1353 /* Readdir access is allowed. */
1354 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1355
1356 /* Write access is forbidden. */
1357 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1358 /* Readdir access is allowed. */
1359 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1360
1361 /*
1362 * Tests shared rule extension: the following rules should not grant
1363 * any new access, only remove some. Once enforced, these rules are
1364 * ANDed with the previous ones.
1365 */
1366 add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
1367 dir_s1d2);
1368 /*
1369 * According to ruleset_fd, dir_s1d2 should now have the
1370 * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE
1371 * access rights (even if this directory is opened a second time).
1372 * However, when enforcing this updated ruleset, the ruleset tied to
1373 * the current process (i.e. its domain) will still only have the
1374 * dir_s1d2 with LANDLOCK_ACCESS_FS_READ_FILE and
1375 * LANDLOCK_ACCESS_FS_READ_DIR accesses, but
1376 * LANDLOCK_ACCESS_FS_WRITE_FILE must not be allowed because it would
1377 * be a privilege escalation.
1378 */
1379 enforce_ruleset(_metadata, ruleset_fd);
1380
1381 /* Same tests and results as above. */
1382 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1383 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1384
1385 /* It is still forbidden to write in file1_s1d2. */
1386 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1387 /* Readdir access is still allowed. */
1388 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1389
1390 /* It is still forbidden to write in file1_s1d3. */
1391 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1392 /* Readdir access is still allowed. */
1393 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1394
1395 /*
1396 * Try to get more privileges by adding new access rights to the parent
1397 * directory: dir_s1d1.
1398 */
1399 add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1);
1400 enforce_ruleset(_metadata, ruleset_fd);
1401
1402 /* Same tests and results as above. */
1403 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1404 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1405
1406 /* It is still forbidden to write in file1_s1d2. */
1407 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1408 /* Readdir access is still allowed. */
1409 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1410
1411 /* It is still forbidden to write in file1_s1d3. */
1412 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1413 /* Readdir access is still allowed. */
1414 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1415
1416 /*
1417 * Now, dir_s1d3 get a new rule tied to it, only allowing
1418 * LANDLOCK_ACCESS_FS_WRITE_FILE. The (kernel internal) difference is
1419 * that there was no rule tied to it before.
1420 */
1421 add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
1422 dir_s1d3);
1423 enforce_ruleset(_metadata, ruleset_fd);
1424 ASSERT_EQ(0, close(ruleset_fd));
1425
1426 /*
1427 * Same tests and results as above, except for open(dir_s1d3) which is
1428 * now denied because the new rule mask the rule previously inherited
1429 * from dir_s1d2.
1430 */
1431
1432 /* Same tests and results as above. */
1433 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
1434 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
1435
1436 /* It is still forbidden to write in file1_s1d2. */
1437 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
1438 /* Readdir access is still allowed. */
1439 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1440
1441 /* It is still forbidden to write in file1_s1d3. */
1442 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
1443 /*
1444 * Readdir of dir_s1d3 is still allowed because of the OR policy inside
1445 * the same layer.
1446 */
1447 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1448 }
1449
TEST_F_FORK(layout1,inherit_superset)1450 TEST_F_FORK(layout1, inherit_superset)
1451 {
1452 const struct rule rules[] = {
1453 {
1454 .path = dir_s1d3,
1455 .access = ACCESS_RO,
1456 },
1457 {},
1458 };
1459 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1460
1461 ASSERT_LE(0, ruleset_fd);
1462 enforce_ruleset(_metadata, ruleset_fd);
1463
1464 /* Readdir access is denied for dir_s1d2. */
1465 ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1466 /* Readdir access is allowed for dir_s1d3. */
1467 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1468 /* File access is allowed for file1_s1d3. */
1469 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1470
1471 /* Now dir_s1d2, parent of dir_s1d3, gets a new rule tied to it. */
1472 add_path_beneath(_metadata, ruleset_fd,
1473 LANDLOCK_ACCESS_FS_READ_FILE |
1474 LANDLOCK_ACCESS_FS_READ_DIR,
1475 dir_s1d2);
1476 enforce_ruleset(_metadata, ruleset_fd);
1477 ASSERT_EQ(0, close(ruleset_fd));
1478
1479 /* Readdir access is still denied for dir_s1d2. */
1480 ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
1481 /* Readdir access is still allowed for dir_s1d3. */
1482 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
1483 /* File access is still allowed for file1_s1d3. */
1484 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
1485 }
1486
TEST_F_FORK(layout0,max_layers)1487 TEST_F_FORK(layout0, max_layers)
1488 {
1489 int i, err;
1490 const struct rule rules[] = {
1491 {
1492 .path = TMP_DIR,
1493 .access = ACCESS_RO,
1494 },
1495 {},
1496 };
1497 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1498
1499 ASSERT_LE(0, ruleset_fd);
1500 for (i = 0; i < 16; i++)
1501 enforce_ruleset(_metadata, ruleset_fd);
1502
1503 for (i = 0; i < 2; i++) {
1504 err = landlock_restrict_self(ruleset_fd, 0);
1505 ASSERT_EQ(-1, err);
1506 ASSERT_EQ(E2BIG, errno);
1507 }
1508 ASSERT_EQ(0, close(ruleset_fd));
1509 }
1510
TEST_F_FORK(layout1,empty_or_same_ruleset)1511 TEST_F_FORK(layout1, empty_or_same_ruleset)
1512 {
1513 struct landlock_ruleset_attr ruleset_attr = {};
1514 int ruleset_fd;
1515
1516 /* Tests empty handled_access_fs. */
1517 ruleset_fd =
1518 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
1519 ASSERT_LE(-1, ruleset_fd);
1520 ASSERT_EQ(ENOMSG, errno);
1521
1522 /* Enforces policy which deny read access to all files. */
1523 ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE;
1524 ruleset_fd =
1525 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
1526 ASSERT_LE(0, ruleset_fd);
1527 enforce_ruleset(_metadata, ruleset_fd);
1528 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
1529 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
1530
1531 /* Nests a policy which deny read access to all directories. */
1532 ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR;
1533 ruleset_fd =
1534 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
1535 ASSERT_LE(0, ruleset_fd);
1536 enforce_ruleset(_metadata, ruleset_fd);
1537 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
1538 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
1539
1540 /* Enforces a second time with the same ruleset. */
1541 enforce_ruleset(_metadata, ruleset_fd);
1542 ASSERT_EQ(0, close(ruleset_fd));
1543 }
1544
TEST_F_FORK(layout1,rule_on_mountpoint)1545 TEST_F_FORK(layout1, rule_on_mountpoint)
1546 {
1547 const struct rule rules[] = {
1548 {
1549 .path = dir_s1d1,
1550 .access = ACCESS_RO,
1551 },
1552 {
1553 /* dir_s3d2 is a mount point. */
1554 .path = dir_s3d2,
1555 .access = ACCESS_RO,
1556 },
1557 {},
1558 };
1559 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1560
1561 ASSERT_LE(0, ruleset_fd);
1562 enforce_ruleset(_metadata, ruleset_fd);
1563 ASSERT_EQ(0, close(ruleset_fd));
1564
1565 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
1566
1567 ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY));
1568
1569 ASSERT_EQ(EACCES, test_open(dir_s3d1, O_RDONLY));
1570 ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));
1571 ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY));
1572 }
1573
TEST_F_FORK(layout1,rule_over_mountpoint)1574 TEST_F_FORK(layout1, rule_over_mountpoint)
1575 {
1576 const struct rule rules[] = {
1577 {
1578 .path = dir_s1d1,
1579 .access = ACCESS_RO,
1580 },
1581 {
1582 /* dir_s3d2 is a mount point. */
1583 .path = dir_s3d1,
1584 .access = ACCESS_RO,
1585 },
1586 {},
1587 };
1588 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1589
1590 ASSERT_LE(0, ruleset_fd);
1591 enforce_ruleset(_metadata, ruleset_fd);
1592 ASSERT_EQ(0, close(ruleset_fd));
1593
1594 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
1595
1596 ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY));
1597
1598 ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY));
1599 ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));
1600 ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY));
1601 }
1602
1603 /*
1604 * This test verifies that we can apply a landlock rule on the root directory
1605 * (which might require special handling).
1606 */
TEST_F_FORK(layout1,rule_over_root_allow_then_deny)1607 TEST_F_FORK(layout1, rule_over_root_allow_then_deny)
1608 {
1609 struct rule rules[] = {
1610 {
1611 .path = "/",
1612 .access = ACCESS_RO,
1613 },
1614 {},
1615 };
1616 int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1617
1618 ASSERT_LE(0, ruleset_fd);
1619 enforce_ruleset(_metadata, ruleset_fd);
1620 ASSERT_EQ(0, close(ruleset_fd));
1621
1622 /* Checks allowed access. */
1623 ASSERT_EQ(0, test_open("/", O_RDONLY));
1624 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
1625
1626 rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE;
1627 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1628 ASSERT_LE(0, ruleset_fd);
1629 enforce_ruleset(_metadata, ruleset_fd);
1630 ASSERT_EQ(0, close(ruleset_fd));
1631
1632 /* Checks denied access (on a directory). */
1633 ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
1634 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
1635 }
1636
TEST_F_FORK(layout1,rule_over_root_deny)1637 TEST_F_FORK(layout1, rule_over_root_deny)
1638 {
1639 const struct rule rules[] = {
1640 {
1641 .path = "/",
1642 .access = LANDLOCK_ACCESS_FS_READ_FILE,
1643 },
1644 {},
1645 };
1646 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1647
1648 ASSERT_LE(0, ruleset_fd);
1649 enforce_ruleset(_metadata, ruleset_fd);
1650 ASSERT_EQ(0, close(ruleset_fd));
1651
1652 /* Checks denied access (on a directory). */
1653 ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
1654 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
1655 }
1656
TEST_F_FORK(layout1,rule_inside_mount_ns)1657 TEST_F_FORK(layout1, rule_inside_mount_ns)
1658 {
1659 const struct rule rules[] = {
1660 {
1661 .path = "s3d3",
1662 .access = ACCESS_RO,
1663 },
1664 {},
1665 };
1666 int ruleset_fd;
1667
1668 set_cap(_metadata, CAP_SYS_ADMIN);
1669 ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3))
1670 {
1671 TH_LOG("Failed to pivot root: %s", strerror(errno));
1672 };
1673 ASSERT_EQ(0, chdir("/"));
1674 clear_cap(_metadata, CAP_SYS_ADMIN);
1675
1676 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1677 ASSERT_LE(0, ruleset_fd);
1678 enforce_ruleset(_metadata, ruleset_fd);
1679 ASSERT_EQ(0, close(ruleset_fd));
1680
1681 ASSERT_EQ(0, test_open("s3d3", O_RDONLY));
1682 ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
1683 }
1684
TEST_F_FORK(layout1,mount_and_pivot)1685 TEST_F_FORK(layout1, mount_and_pivot)
1686 {
1687 const struct rule rules[] = {
1688 {
1689 .path = dir_s3d2,
1690 .access = ACCESS_RO,
1691 },
1692 {},
1693 };
1694 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1695
1696 ASSERT_LE(0, ruleset_fd);
1697 enforce_ruleset(_metadata, ruleset_fd);
1698 ASSERT_EQ(0, close(ruleset_fd));
1699
1700 set_cap(_metadata, CAP_SYS_ADMIN);
1701 ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL));
1702 ASSERT_EQ(EPERM, errno);
1703 ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3));
1704 ASSERT_EQ(EPERM, errno);
1705 clear_cap(_metadata, CAP_SYS_ADMIN);
1706 }
1707
TEST_F_FORK(layout1,move_mount)1708 TEST_F_FORK(layout1, move_mount)
1709 {
1710 const struct rule rules[] = {
1711 {
1712 .path = dir_s3d2,
1713 .access = ACCESS_RO,
1714 },
1715 {},
1716 };
1717 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1718
1719 ASSERT_LE(0, ruleset_fd);
1720
1721 set_cap(_metadata, CAP_SYS_ADMIN);
1722 ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
1723 dir_s1d2, 0))
1724 {
1725 TH_LOG("Failed to move mount: %s", strerror(errno));
1726 }
1727
1728 ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD,
1729 dir_s3d2, 0));
1730 clear_cap(_metadata, CAP_SYS_ADMIN);
1731
1732 enforce_ruleset(_metadata, ruleset_fd);
1733 ASSERT_EQ(0, close(ruleset_fd));
1734
1735 set_cap(_metadata, CAP_SYS_ADMIN);
1736 ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
1737 dir_s1d2, 0));
1738 ASSERT_EQ(EPERM, errno);
1739 clear_cap(_metadata, CAP_SYS_ADMIN);
1740 }
1741
TEST_F_FORK(layout1,topology_changes_with_net_only)1742 TEST_F_FORK(layout1, topology_changes_with_net_only)
1743 {
1744 const struct landlock_ruleset_attr ruleset_net = {
1745 .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
1746 LANDLOCK_ACCESS_NET_CONNECT_TCP,
1747 };
1748 int ruleset_fd;
1749
1750 /* Add network restrictions. */
1751 ruleset_fd =
1752 landlock_create_ruleset(&ruleset_net, sizeof(ruleset_net), 0);
1753 ASSERT_LE(0, ruleset_fd);
1754 enforce_ruleset(_metadata, ruleset_fd);
1755 ASSERT_EQ(0, close(ruleset_fd));
1756
1757 /* Mount, remount, move_mount, umount, and pivot_root checks. */
1758 set_cap(_metadata, CAP_SYS_ADMIN);
1759 ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s1d2));
1760 ASSERT_EQ(0, mount(NULL, dir_s1d2, NULL, MS_PRIVATE | MS_REC, NULL));
1761 ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD,
1762 dir_s2d2, 0));
1763 ASSERT_EQ(0, umount(dir_s2d2));
1764 ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3));
1765 ASSERT_EQ(0, chdir("/"));
1766 clear_cap(_metadata, CAP_SYS_ADMIN);
1767 }
1768
TEST_F_FORK(layout1,topology_changes_with_net_and_fs)1769 TEST_F_FORK(layout1, topology_changes_with_net_and_fs)
1770 {
1771 const struct landlock_ruleset_attr ruleset_net_fs = {
1772 .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
1773 LANDLOCK_ACCESS_NET_CONNECT_TCP,
1774 .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
1775 };
1776 int ruleset_fd;
1777
1778 /* Add network and filesystem restrictions. */
1779 ruleset_fd = landlock_create_ruleset(&ruleset_net_fs,
1780 sizeof(ruleset_net_fs), 0);
1781 ASSERT_LE(0, ruleset_fd);
1782 enforce_ruleset(_metadata, ruleset_fd);
1783 ASSERT_EQ(0, close(ruleset_fd));
1784
1785 /* Mount, remount, move_mount, umount, and pivot_root checks. */
1786 set_cap(_metadata, CAP_SYS_ADMIN);
1787 ASSERT_EQ(-1, mount_opt(&mnt_tmp, dir_s1d2));
1788 ASSERT_EQ(EPERM, errno);
1789 ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_PRIVATE | MS_REC, NULL));
1790 ASSERT_EQ(EPERM, errno);
1791 ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
1792 dir_s2d2, 0));
1793 ASSERT_EQ(EPERM, errno);
1794 ASSERT_EQ(-1, umount(dir_s3d2));
1795 ASSERT_EQ(EPERM, errno);
1796 ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3));
1797 ASSERT_EQ(EPERM, errno);
1798 clear_cap(_metadata, CAP_SYS_ADMIN);
1799 }
1800
TEST_F_FORK(layout1,release_inodes)1801 TEST_F_FORK(layout1, release_inodes)
1802 {
1803 const struct rule rules[] = {
1804 {
1805 .path = dir_s1d1,
1806 .access = ACCESS_RO,
1807 },
1808 {
1809 .path = dir_s3d2,
1810 .access = ACCESS_RO,
1811 },
1812 {
1813 .path = dir_s3d3,
1814 .access = ACCESS_RO,
1815 },
1816 {},
1817 };
1818 const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
1819
1820 ASSERT_LE(0, ruleset_fd);
1821 /* Unmount a file hierarchy while it is being used by a ruleset. */
1822 set_cap(_metadata, CAP_SYS_ADMIN);
1823 ASSERT_EQ(0, umount(dir_s3d2));
1824 clear_cap(_metadata, CAP_SYS_ADMIN);
1825
1826 enforce_ruleset(_metadata, ruleset_fd);
1827 ASSERT_EQ(0, close(ruleset_fd));
1828
1829 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
1830 ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY));
1831 /* This dir_s3d3 would not be allowed and does not exist anyway. */
1832 ASSERT_EQ(ENOENT, test_open(dir_s3d3, O_RDONLY));
1833 }
1834
1835 /*
1836 * This test checks that a rule on a directory used as a mount point does not
1837 * grant access to the mount covering it. It is a generalization of the bind
1838 * mount case in layout3_fs.hostfs.release_inodes that tests hidden mount points.
1839 */
TEST_F_FORK(layout1,covered_rule)1840 TEST_F_FORK(layout1, covered_rule)
1841 {
1842 const struct rule layer1[] = {
1843 {
1844 .path = dir_s3d2,
1845 .access = LANDLOCK_ACCESS_FS_READ_DIR,
1846 },
1847 {},
1848 };
1849 int ruleset_fd;
1850
1851 /* Unmount to simplify FIXTURE_TEARDOWN. */
1852 set_cap(_metadata, CAP_SYS_ADMIN);
1853 ASSERT_EQ(0, umount(dir_s3d2));
1854 clear_cap(_metadata, CAP_SYS_ADMIN);
1855
1856 /* Creates a ruleset with the future hidden directory. */
1857 ruleset_fd =
1858 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
1859 ASSERT_LE(0, ruleset_fd);
1860
1861 /* Covers with a new mount point. */
1862 set_cap(_metadata, CAP_SYS_ADMIN);
1863 ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
1864 clear_cap(_metadata, CAP_SYS_ADMIN);
1865
1866 ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));
1867
1868 enforce_ruleset(_metadata, ruleset_fd);
1869 ASSERT_EQ(0, close(ruleset_fd));
1870
1871 /* Checks that access to the new mount point is denied. */
1872 ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY));
1873 }
1874
1875 enum relative_access {
1876 REL_OPEN,
1877 REL_CHDIR,
1878 REL_CHROOT_ONLY,
1879 REL_CHROOT_CHDIR,
1880 };
1881
test_relative_path(struct __test_metadata * const _metadata,const enum relative_access rel)1882 static void test_relative_path(struct __test_metadata *const _metadata,
1883 const enum relative_access rel)
1884 {
1885 /*
1886 * Common layer to check that chroot doesn't ignore it (i.e. a chroot
1887 * is not a disconnected root directory).
1888 */
1889 const struct rule layer1_base[] = {
1890 {
1891 .path = TMP_DIR,
1892 .access = ACCESS_RO,
1893 },
1894 {},
1895 };
1896 const struct rule layer2_subs[] = {
1897 {
1898 .path = dir_s1d2,
1899 .access = ACCESS_RO,
1900 },
1901 {
1902 .path = dir_s2d2,
1903 .access = ACCESS_RO,
1904 },
1905 {},
1906 };
1907 int dirfd, ruleset_fd;
1908
1909 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base);
1910 ASSERT_LE(0, ruleset_fd);
1911 enforce_ruleset(_metadata, ruleset_fd);
1912 ASSERT_EQ(0, close(ruleset_fd));
1913
1914 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs);
1915
1916 ASSERT_LE(0, ruleset_fd);
1917 switch (rel) {
1918 case REL_OPEN:
1919 case REL_CHDIR:
1920 break;
1921 case REL_CHROOT_ONLY:
1922 ASSERT_EQ(0, chdir(dir_s2d2));
1923 break;
1924 case REL_CHROOT_CHDIR:
1925 ASSERT_EQ(0, chdir(dir_s1d2));
1926 break;
1927 default:
1928 ASSERT_TRUE(false);
1929 return;
1930 }
1931
1932 set_cap(_metadata, CAP_SYS_CHROOT);
1933 enforce_ruleset(_metadata, ruleset_fd);
1934
1935 switch (rel) {
1936 case REL_OPEN:
1937 dirfd = open(dir_s1d2, O_DIRECTORY);
1938 ASSERT_LE(0, dirfd);
1939 break;
1940 case REL_CHDIR:
1941 ASSERT_EQ(0, chdir(dir_s1d2));
1942 dirfd = AT_FDCWD;
1943 break;
1944 case REL_CHROOT_ONLY:
1945 /* Do chroot into dir_s1d2 (relative to dir_s2d2). */
1946 ASSERT_EQ(0, chroot("../../s1d1/s1d2"))
1947 {
1948 TH_LOG("Failed to chroot: %s", strerror(errno));
1949 }
1950 dirfd = AT_FDCWD;
1951 break;
1952 case REL_CHROOT_CHDIR:
1953 /* Do chroot into dir_s1d2. */
1954 ASSERT_EQ(0, chroot("."))
1955 {
1956 TH_LOG("Failed to chroot: %s", strerror(errno));
1957 }
1958 dirfd = AT_FDCWD;
1959 break;
1960 }
1961
1962 ASSERT_EQ((rel == REL_CHROOT_CHDIR) ? 0 : EACCES,
1963 test_open_rel(dirfd, "..", O_RDONLY));
1964 ASSERT_EQ(0, test_open_rel(dirfd, ".", O_RDONLY));
1965
1966 if (rel == REL_CHROOT_ONLY) {
1967 /* The current directory is dir_s2d2. */
1968 ASSERT_EQ(0, test_open_rel(dirfd, "./s2d3", O_RDONLY));
1969 } else {
1970 /* The current directory is dir_s1d2. */
1971 ASSERT_EQ(0, test_open_rel(dirfd, "./s1d3", O_RDONLY));
1972 }
1973
1974 if (rel == REL_CHROOT_ONLY || rel == REL_CHROOT_CHDIR) {
1975 /* Checks the root dir_s1d2. */
1976 ASSERT_EQ(0, test_open_rel(dirfd, "/..", O_RDONLY));
1977 ASSERT_EQ(0, test_open_rel(dirfd, "/", O_RDONLY));
1978 ASSERT_EQ(0, test_open_rel(dirfd, "/f1", O_RDONLY));
1979 ASSERT_EQ(0, test_open_rel(dirfd, "/s1d3", O_RDONLY));
1980 }
1981
1982 if (rel != REL_CHROOT_CHDIR) {
1983 ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s1d1", O_RDONLY));
1984 ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2", O_RDONLY));
1985 ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3",
1986 O_RDONLY));
1987
1988 ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s2d1", O_RDONLY));
1989 ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2", O_RDONLY));
1990 ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3",
1991 O_RDONLY));
1992 }
1993
1994 if (rel == REL_OPEN)
1995 ASSERT_EQ(0, close(dirfd));
1996 ASSERT_EQ(0, close(ruleset_fd));
1997 }
1998
TEST_F_FORK(layout1,relative_open)1999 TEST_F_FORK(layout1, relative_open)
2000 {
2001 test_relative_path(_metadata, REL_OPEN);
2002 }
2003
TEST_F_FORK(layout1,relative_chdir)2004 TEST_F_FORK(layout1, relative_chdir)
2005 {
2006 test_relative_path(_metadata, REL_CHDIR);
2007 }
2008
TEST_F_FORK(layout1,relative_chroot_only)2009 TEST_F_FORK(layout1, relative_chroot_only)
2010 {
2011 test_relative_path(_metadata, REL_CHROOT_ONLY);
2012 }
2013
TEST_F_FORK(layout1,relative_chroot_chdir)2014 TEST_F_FORK(layout1, relative_chroot_chdir)
2015 {
2016 test_relative_path(_metadata, REL_CHROOT_CHDIR);
2017 }
2018
copy_file(struct __test_metadata * const _metadata,const char * const src_path,const char * const dst_path)2019 static void copy_file(struct __test_metadata *const _metadata,
2020 const char *const src_path, const char *const dst_path)
2021 {
2022 int dst_fd, src_fd;
2023 struct stat statbuf;
2024
2025 dst_fd = open(dst_path, O_WRONLY | O_TRUNC | O_CLOEXEC);
2026 ASSERT_LE(0, dst_fd)
2027 {
2028 TH_LOG("Failed to open \"%s\": %s", dst_path, strerror(errno));
2029 }
2030 src_fd = open(src_path, O_RDONLY | O_CLOEXEC);
2031 ASSERT_LE(0, src_fd)
2032 {
2033 TH_LOG("Failed to open \"%s\": %s", src_path, strerror(errno));
2034 }
2035 ASSERT_EQ(0, fstat(src_fd, &statbuf));
2036 ASSERT_EQ(statbuf.st_size,
2037 sendfile(dst_fd, src_fd, 0, statbuf.st_size));
2038 ASSERT_EQ(0, close(src_fd));
2039 ASSERT_EQ(0, close(dst_fd));
2040 }
2041
test_execute(struct __test_metadata * const _metadata,const int err,const char * const path)2042 static void test_execute(struct __test_metadata *const _metadata, const int err,
2043 const char *const path)
2044 {
2045 int status;
2046 char *const argv[] = { (char *)path, NULL };
2047 const pid_t child = fork();
2048
2049 ASSERT_LE(0, child);
2050 if (child == 0) {
2051 ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL))
2052 {
2053 TH_LOG("Failed to execute \"%s\": %s", path,
2054 strerror(errno));
2055 };
2056 ASSERT_EQ(err, errno);
2057 _exit(__test_passed(_metadata) ? 2 : 1);
2058 return;
2059 }
2060 ASSERT_EQ(child, waitpid(child, &status, 0));
2061 ASSERT_EQ(1, WIFEXITED(status));
2062 ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status))
2063 {
2064 TH_LOG("Unexpected return code for \"%s\"", path);
2065 };
2066 }
2067
test_check_exec(struct __test_metadata * const _metadata,const int err,const char * const path)2068 static void test_check_exec(struct __test_metadata *const _metadata,
2069 const int err, const char *const path)
2070 {
2071 int ret;
2072 char *const argv[] = { (char *)path, NULL };
2073
2074 ret = sys_execveat(AT_FDCWD, path, argv, NULL,
2075 AT_EMPTY_PATH | AT_EXECVE_CHECK);
2076 if (err) {
2077 EXPECT_EQ(-1, ret);
2078 EXPECT_EQ(errno, err);
2079 } else {
2080 EXPECT_EQ(0, ret);
2081 }
2082 }
2083
TEST_F_FORK(layout1,execute)2084 TEST_F_FORK(layout1, execute)
2085 {
2086 const struct rule rules[] = {
2087 {
2088 .path = dir_s1d2,
2089 .access = LANDLOCK_ACCESS_FS_EXECUTE,
2090 },
2091 {},
2092 };
2093 const int ruleset_fd =
2094 create_ruleset(_metadata, rules[0].access, rules);
2095
2096 ASSERT_LE(0, ruleset_fd);
2097 copy_file(_metadata, bin_true, file1_s1d1);
2098 copy_file(_metadata, bin_true, file1_s1d2);
2099 copy_file(_metadata, bin_true, file1_s1d3);
2100
2101 /* Checks before file1_s1d1 being denied. */
2102 test_execute(_metadata, 0, file1_s1d1);
2103 test_check_exec(_metadata, 0, file1_s1d1);
2104
2105 enforce_ruleset(_metadata, ruleset_fd);
2106 ASSERT_EQ(0, close(ruleset_fd));
2107
2108 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
2109 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
2110 test_execute(_metadata, EACCES, file1_s1d1);
2111 test_check_exec(_metadata, EACCES, file1_s1d1);
2112
2113 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
2114 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
2115 test_execute(_metadata, 0, file1_s1d2);
2116 test_check_exec(_metadata, 0, file1_s1d2);
2117
2118 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY));
2119 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
2120 test_execute(_metadata, 0, file1_s1d3);
2121 test_check_exec(_metadata, 0, file1_s1d3);
2122 }
2123
TEST_F_FORK(layout1,umount_sandboxer)2124 TEST_F_FORK(layout1, umount_sandboxer)
2125 {
2126 int pipe_child[2], pipe_parent[2];
2127 char buf_parent;
2128 pid_t child;
2129 int status;
2130
2131 copy_file(_metadata, bin_sandbox_and_launch, file1_s3d3);
2132 ASSERT_EQ(0, pipe2(pipe_child, 0));
2133 ASSERT_EQ(0, pipe2(pipe_parent, 0));
2134
2135 child = fork();
2136 ASSERT_LE(0, child);
2137 if (child == 0) {
2138 char pipe_child_str[12], pipe_parent_str[12];
2139 char *const argv[] = { (char *)file1_s3d3,
2140 (char *)bin_wait_pipe, pipe_child_str,
2141 pipe_parent_str, NULL };
2142
2143 /* Passes the pipe FDs to the executed binary and its child. */
2144 EXPECT_EQ(0, close(pipe_child[0]));
2145 EXPECT_EQ(0, close(pipe_parent[1]));
2146 snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
2147 pipe_child[1]);
2148 snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
2149 pipe_parent[0]);
2150
2151 /*
2152 * We need bin_sandbox_and_launch (copied inside the mount as
2153 * file1_s3d3) to execute bin_wait_pipe (outside the mount) to
2154 * make sure the mount point will not be EBUSY because of
2155 * file1_s3d3 being in use. This avoids a potential race
2156 * condition between the following read() and umount() calls.
2157 */
2158 ASSERT_EQ(0, execve(argv[0], argv, NULL))
2159 {
2160 TH_LOG("Failed to execute \"%s\": %s", argv[0],
2161 strerror(errno));
2162 };
2163 _exit(1);
2164 return;
2165 }
2166
2167 EXPECT_EQ(0, close(pipe_child[1]));
2168 EXPECT_EQ(0, close(pipe_parent[0]));
2169
2170 /* Waits for the child to sandbox itself. */
2171 EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
2172
2173 /* Tests that the sandboxer is tied to its mount point. */
2174 set_cap(_metadata, CAP_SYS_ADMIN);
2175 EXPECT_EQ(-1, umount(dir_s3d2));
2176 EXPECT_EQ(EBUSY, errno);
2177 clear_cap(_metadata, CAP_SYS_ADMIN);
2178
2179 /* Signals the child to launch a grandchild. */
2180 EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
2181
2182 /* Waits for the grandchild. */
2183 EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));
2184
2185 /* Tests that the domain's sandboxer is not tied to its mount point. */
2186 set_cap(_metadata, CAP_SYS_ADMIN);
2187 EXPECT_EQ(0, umount(dir_s3d2))
2188 {
2189 TH_LOG("Failed to umount \"%s\": %s", dir_s3d2,
2190 strerror(errno));
2191 };
2192 clear_cap(_metadata, CAP_SYS_ADMIN);
2193
2194 /* Signals the grandchild to terminate. */
2195 EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
2196 ASSERT_EQ(child, waitpid(child, &status, 0));
2197 ASSERT_EQ(1, WIFEXITED(status));
2198 ASSERT_EQ(0, WEXITSTATUS(status));
2199 }
2200
TEST_F_FORK(layout1,link)2201 TEST_F_FORK(layout1, link)
2202 {
2203 const struct rule layer1[] = {
2204 {
2205 .path = dir_s1d2,
2206 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2207 },
2208 {},
2209 };
2210 const struct rule layer2[] = {
2211 {
2212 .path = dir_s1d3,
2213 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
2214 },
2215 {},
2216 };
2217 int ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
2218
2219 ASSERT_LE(0, ruleset_fd);
2220
2221 ASSERT_EQ(0, unlink(file1_s1d1));
2222 ASSERT_EQ(0, unlink(file1_s1d2));
2223 ASSERT_EQ(0, unlink(file1_s1d3));
2224
2225 enforce_ruleset(_metadata, ruleset_fd);
2226 ASSERT_EQ(0, close(ruleset_fd));
2227
2228 ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
2229 ASSERT_EQ(EACCES, errno);
2230
2231 /* Denies linking because of reparenting. */
2232 ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2));
2233 ASSERT_EQ(EXDEV, errno);
2234 ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3));
2235 ASSERT_EQ(EXDEV, errno);
2236 ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2));
2237 ASSERT_EQ(EXDEV, errno);
2238
2239 ASSERT_EQ(0, link(file2_s1d2, file1_s1d2));
2240 ASSERT_EQ(0, link(file2_s1d3, file1_s1d3));
2241
2242 /* Prepares for next unlinks. */
2243 ASSERT_EQ(0, unlink(file2_s1d2));
2244 ASSERT_EQ(0, unlink(file2_s1d3));
2245
2246 ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
2247 ASSERT_LE(0, ruleset_fd);
2248 enforce_ruleset(_metadata, ruleset_fd);
2249 ASSERT_EQ(0, close(ruleset_fd));
2250
2251 /* Checks that linkind doesn't require the ability to delete a file. */
2252 ASSERT_EQ(0, link(file1_s1d2, file2_s1d2));
2253 ASSERT_EQ(0, link(file1_s1d3, file2_s1d3));
2254 }
2255
test_rename(const char * const oldpath,const char * const newpath)2256 static int test_rename(const char *const oldpath, const char *const newpath)
2257 {
2258 if (rename(oldpath, newpath))
2259 return errno;
2260 return 0;
2261 }
2262
test_exchange(const char * const oldpath,const char * const newpath)2263 static int test_exchange(const char *const oldpath, const char *const newpath)
2264 {
2265 if (renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE))
2266 return errno;
2267 return 0;
2268 }
2269
test_renameat(int olddirfd,const char * oldpath,int newdirfd,const char * newpath)2270 static int test_renameat(int olddirfd, const char *oldpath, int newdirfd,
2271 const char *newpath)
2272 {
2273 if (renameat2(olddirfd, oldpath, newdirfd, newpath, 0))
2274 return errno;
2275 return 0;
2276 }
2277
test_exchangeat(int olddirfd,const char * oldpath,int newdirfd,const char * newpath)2278 static int test_exchangeat(int olddirfd, const char *oldpath, int newdirfd,
2279 const char *newpath)
2280 {
2281 if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_EXCHANGE))
2282 return errno;
2283 return 0;
2284 }
2285
TEST_F_FORK(layout1,rename_file)2286 TEST_F_FORK(layout1, rename_file)
2287 {
2288 const struct rule rules[] = {
2289 {
2290 .path = dir_s1d3,
2291 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
2292 },
2293 {
2294 .path = dir_s2d2,
2295 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
2296 },
2297 {},
2298 };
2299 const int ruleset_fd =
2300 create_ruleset(_metadata, rules[0].access, rules);
2301
2302 ASSERT_LE(0, ruleset_fd);
2303
2304 ASSERT_EQ(0, unlink(file1_s1d2));
2305
2306 enforce_ruleset(_metadata, ruleset_fd);
2307 ASSERT_EQ(0, close(ruleset_fd));
2308
2309 /*
2310 * Tries to replace a file, from a directory that allows file removal,
2311 * but to a different directory (which also allows file removal).
2312 */
2313 ASSERT_EQ(-1, rename(file1_s2d3, file1_s1d3));
2314 ASSERT_EQ(EXDEV, errno);
2315 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d3,
2316 RENAME_EXCHANGE));
2317 ASSERT_EQ(EXDEV, errno);
2318 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3,
2319 RENAME_EXCHANGE));
2320 ASSERT_EQ(EXDEV, errno);
2321
2322 /*
2323 * Tries to replace a file, from a directory that denies file removal,
2324 * to a different directory (which allows file removal).
2325 */
2326 ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
2327 ASSERT_EQ(EACCES, errno);
2328 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3,
2329 RENAME_EXCHANGE));
2330 ASSERT_EQ(EACCES, errno);
2331 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s1d3,
2332 RENAME_EXCHANGE));
2333 ASSERT_EQ(EXDEV, errno);
2334
2335 /* Exchanges files and directories that partially allow removal. */
2336 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s2d1,
2337 RENAME_EXCHANGE));
2338 ASSERT_EQ(EACCES, errno);
2339 /* Checks that file1_s2d1 cannot be removed (instead of ENOTDIR). */
2340 ASSERT_EQ(-1, rename(dir_s2d2, file1_s2d1));
2341 ASSERT_EQ(EACCES, errno);
2342 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2,
2343 RENAME_EXCHANGE));
2344 ASSERT_EQ(EACCES, errno);
2345 /* Checks that file1_s1d1 cannot be removed (instead of EISDIR). */
2346 ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2));
2347 ASSERT_EQ(EACCES, errno);
2348
2349 /* Renames files with different parents. */
2350 ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2));
2351 ASSERT_EQ(EXDEV, errno);
2352 ASSERT_EQ(0, unlink(file1_s1d3));
2353 ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
2354 ASSERT_EQ(EACCES, errno);
2355
2356 /* Exchanges and renames files with same parent. */
2357 ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3,
2358 RENAME_EXCHANGE));
2359 ASSERT_EQ(0, rename(file2_s2d3, file1_s2d3));
2360
2361 /* Exchanges files and directories with same parent, twice. */
2362 ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3,
2363 RENAME_EXCHANGE));
2364 ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3,
2365 RENAME_EXCHANGE));
2366 }
2367
TEST_F_FORK(layout1,rename_dir)2368 TEST_F_FORK(layout1, rename_dir)
2369 {
2370 const struct rule rules[] = {
2371 {
2372 .path = dir_s1d2,
2373 .access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
2374 },
2375 {
2376 .path = dir_s2d1,
2377 .access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
2378 },
2379 {},
2380 };
2381 const int ruleset_fd =
2382 create_ruleset(_metadata, rules[0].access, rules);
2383
2384 ASSERT_LE(0, ruleset_fd);
2385
2386 /* Empties dir_s1d3 to allow renaming. */
2387 ASSERT_EQ(0, unlink(file1_s1d3));
2388 ASSERT_EQ(0, unlink(file2_s1d3));
2389
2390 enforce_ruleset(_metadata, ruleset_fd);
2391 ASSERT_EQ(0, close(ruleset_fd));
2392
2393 /* Exchanges and renames directory to a different parent. */
2394 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
2395 RENAME_EXCHANGE));
2396 ASSERT_EQ(EXDEV, errno);
2397 ASSERT_EQ(-1, rename(dir_s2d3, dir_s1d3));
2398 ASSERT_EQ(EXDEV, errno);
2399 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
2400 RENAME_EXCHANGE));
2401 ASSERT_EQ(EXDEV, errno);
2402
2403 /*
2404 * Exchanges directory to the same parent, which doesn't allow
2405 * directory removal.
2406 */
2407 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d1, AT_FDCWD, dir_s2d1,
2408 RENAME_EXCHANGE));
2409 ASSERT_EQ(EACCES, errno);
2410 /* Checks that dir_s1d2 cannot be removed (instead of ENOTDIR). */
2411 ASSERT_EQ(-1, rename(dir_s1d2, file1_s1d1));
2412 ASSERT_EQ(EACCES, errno);
2413 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2,
2414 RENAME_EXCHANGE));
2415 ASSERT_EQ(EACCES, errno);
2416 /* Checks that dir_s1d2 cannot be removed (instead of EISDIR). */
2417 ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2));
2418 ASSERT_EQ(EACCES, errno);
2419
2420 /*
2421 * Exchanges and renames directory to the same parent, which allows
2422 * directory removal.
2423 */
2424 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s1d2,
2425 RENAME_EXCHANGE));
2426 ASSERT_EQ(0, unlink(dir_s1d3));
2427 ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
2428 ASSERT_EQ(0, rename(file1_s1d2, dir_s1d3));
2429 ASSERT_EQ(0, rmdir(dir_s1d3));
2430 }
2431
TEST_F_FORK(layout1,reparent_refer)2432 TEST_F_FORK(layout1, reparent_refer)
2433 {
2434 const struct rule layer1[] = {
2435 {
2436 .path = dir_s1d2,
2437 .access = LANDLOCK_ACCESS_FS_REFER,
2438 },
2439 {
2440 .path = dir_s2d2,
2441 .access = LANDLOCK_ACCESS_FS_REFER,
2442 },
2443 {},
2444 };
2445 int ruleset_fd =
2446 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
2447
2448 ASSERT_LE(0, ruleset_fd);
2449 enforce_ruleset(_metadata, ruleset_fd);
2450 ASSERT_EQ(0, close(ruleset_fd));
2451
2452 ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1));
2453 ASSERT_EQ(EXDEV, errno);
2454 ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2));
2455 ASSERT_EQ(EXDEV, errno);
2456 ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
2457 ASSERT_EQ(EXDEV, errno);
2458
2459 ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d1));
2460 ASSERT_EQ(EXDEV, errno);
2461 ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d2));
2462 ASSERT_EQ(EXDEV, errno);
2463 /*
2464 * Moving should only be allowed when the source and the destination
2465 * parent directory have REFER.
2466 */
2467 ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3));
2468 ASSERT_EQ(ENOTEMPTY, errno);
2469 ASSERT_EQ(0, unlink(file1_s2d3));
2470 ASSERT_EQ(0, unlink(file2_s2d3));
2471 ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3));
2472 }
2473
2474 /* Checks renames beneath dir_s1d1. */
refer_denied_by_default(struct __test_metadata * const _metadata,const struct rule layer1[],const int layer1_err,const struct rule layer2[])2475 static void refer_denied_by_default(struct __test_metadata *const _metadata,
2476 const struct rule layer1[],
2477 const int layer1_err,
2478 const struct rule layer2[])
2479 {
2480 int ruleset_fd;
2481
2482 ASSERT_EQ(0, unlink(file1_s1d2));
2483
2484 ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
2485 ASSERT_LE(0, ruleset_fd);
2486 enforce_ruleset(_metadata, ruleset_fd);
2487 ASSERT_EQ(0, close(ruleset_fd));
2488
2489 /*
2490 * If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to
2491 * layer1_err), then it allows some different-parent renames and links.
2492 */
2493 ASSERT_EQ(layer1_err, test_rename(file1_s1d1, file1_s1d2));
2494 if (layer1_err == 0)
2495 ASSERT_EQ(layer1_err, test_rename(file1_s1d2, file1_s1d1));
2496 ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2));
2497 ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1));
2498
2499 ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
2500 ASSERT_LE(0, ruleset_fd);
2501 enforce_ruleset(_metadata, ruleset_fd);
2502 ASSERT_EQ(0, close(ruleset_fd));
2503
2504 /*
2505 * Now, either the first or the second layer does not handle
2506 * LANDLOCK_ACCESS_FS_REFER, which means that any different-parent
2507 * renames and links are denied, thus making the layer handling
2508 * LANDLOCK_ACCESS_FS_REFER null and void.
2509 */
2510 ASSERT_EQ(EXDEV, test_rename(file1_s1d1, file1_s1d2));
2511 ASSERT_EQ(EXDEV, test_exchange(file2_s1d1, file2_s1d2));
2512 ASSERT_EQ(EXDEV, test_exchange(file2_s1d2, file2_s1d1));
2513 }
2514
2515 const struct rule layer_dir_s1d1_refer[] = {
2516 {
2517 .path = dir_s1d1,
2518 .access = LANDLOCK_ACCESS_FS_REFER,
2519 },
2520 {},
2521 };
2522
2523 const struct rule layer_dir_s1d1_execute[] = {
2524 {
2525 /* Matches a parent directory. */
2526 .path = dir_s1d1,
2527 .access = LANDLOCK_ACCESS_FS_EXECUTE,
2528 },
2529 {},
2530 };
2531
2532 const struct rule layer_dir_s2d1_execute[] = {
2533 {
2534 /* Does not match a parent directory. */
2535 .path = dir_s2d1,
2536 .access = LANDLOCK_ACCESS_FS_EXECUTE,
2537 },
2538 {},
2539 };
2540
2541 /*
2542 * Tests precedence over renames: denied by default for different parent
2543 * directories, *with* a rule matching a parent directory, but not directly
2544 * denying access (with MAKE_REG nor REMOVE).
2545 */
TEST_F_FORK(layout1,refer_denied_by_default1)2546 TEST_F_FORK(layout1, refer_denied_by_default1)
2547 {
2548 refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
2549 layer_dir_s1d1_execute);
2550 }
2551
2552 /*
2553 * Same test but this time turning around the ABI version order: the first
2554 * layer does not handle LANDLOCK_ACCESS_FS_REFER.
2555 */
TEST_F_FORK(layout1,refer_denied_by_default2)2556 TEST_F_FORK(layout1, refer_denied_by_default2)
2557 {
2558 refer_denied_by_default(_metadata, layer_dir_s1d1_execute, EXDEV,
2559 layer_dir_s1d1_refer);
2560 }
2561
2562 /*
2563 * Tests precedence over renames: denied by default for different parent
2564 * directories, *without* a rule matching a parent directory, but not directly
2565 * denying access (with MAKE_REG nor REMOVE).
2566 */
TEST_F_FORK(layout1,refer_denied_by_default3)2567 TEST_F_FORK(layout1, refer_denied_by_default3)
2568 {
2569 refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
2570 layer_dir_s2d1_execute);
2571 }
2572
2573 /*
2574 * Same test but this time turning around the ABI version order: the first
2575 * layer does not handle LANDLOCK_ACCESS_FS_REFER.
2576 */
TEST_F_FORK(layout1,refer_denied_by_default4)2577 TEST_F_FORK(layout1, refer_denied_by_default4)
2578 {
2579 refer_denied_by_default(_metadata, layer_dir_s2d1_execute, EXDEV,
2580 layer_dir_s1d1_refer);
2581 }
2582
2583 /*
2584 * Tests walking through a denied root mount.
2585 */
TEST_F_FORK(layout1,refer_mount_root_deny)2586 TEST_F_FORK(layout1, refer_mount_root_deny)
2587 {
2588 const struct landlock_ruleset_attr ruleset_attr = {
2589 .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_DIR,
2590 };
2591 int root_fd, ruleset_fd;
2592
2593 /* Creates a mount object from a non-mount point. */
2594 set_cap(_metadata, CAP_SYS_ADMIN);
2595 root_fd =
2596 open_tree(AT_FDCWD, dir_s1d1,
2597 AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
2598 clear_cap(_metadata, CAP_SYS_ADMIN);
2599 ASSERT_LE(0, root_fd);
2600
2601 ruleset_fd =
2602 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
2603 ASSERT_LE(0, ruleset_fd);
2604
2605 ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
2606 ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
2607 EXPECT_EQ(0, close(ruleset_fd));
2608
2609 /* Link denied by Landlock: EACCES. */
2610 EXPECT_EQ(-1, linkat(root_fd, ".", root_fd, "does_not_exist", 0));
2611 EXPECT_EQ(EACCES, errno);
2612
2613 /* renameat2() always returns EBUSY. */
2614 EXPECT_EQ(-1, renameat2(root_fd, ".", root_fd, "does_not_exist", 0));
2615 EXPECT_EQ(EBUSY, errno);
2616
2617 EXPECT_EQ(0, close(root_fd));
2618 }
2619
TEST_F_FORK(layout1,refer_part_mount_tree_is_allowed)2620 TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
2621 {
2622 const struct rule layer1[] = {
2623 {
2624 /* Parent mount point. */
2625 .path = dir_s3d1,
2626 .access = LANDLOCK_ACCESS_FS_REFER |
2627 LANDLOCK_ACCESS_FS_MAKE_REG,
2628 },
2629 {
2630 /*
2631 * Removing the source file is allowed because its
2632 * access rights are already a superset of the
2633 * destination.
2634 */
2635 .path = dir_s3d4,
2636 .access = LANDLOCK_ACCESS_FS_REFER |
2637 LANDLOCK_ACCESS_FS_MAKE_REG |
2638 LANDLOCK_ACCESS_FS_REMOVE_FILE,
2639 },
2640 {},
2641 };
2642 int ruleset_fd;
2643
2644 ASSERT_EQ(0, unlink(file1_s3d3));
2645 ruleset_fd = create_ruleset(_metadata,
2646 LANDLOCK_ACCESS_FS_REFER |
2647 LANDLOCK_ACCESS_FS_MAKE_REG |
2648 LANDLOCK_ACCESS_FS_REMOVE_FILE,
2649 layer1);
2650
2651 ASSERT_LE(0, ruleset_fd);
2652 enforce_ruleset(_metadata, ruleset_fd);
2653 ASSERT_EQ(0, close(ruleset_fd));
2654
2655 ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
2656 }
2657
TEST_F_FORK(layout1,reparent_link)2658 TEST_F_FORK(layout1, reparent_link)
2659 {
2660 const struct rule layer1[] = {
2661 {
2662 .path = dir_s1d2,
2663 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2664 },
2665 {
2666 .path = dir_s1d3,
2667 .access = LANDLOCK_ACCESS_FS_REFER,
2668 },
2669 {
2670 .path = dir_s2d2,
2671 .access = LANDLOCK_ACCESS_FS_REFER,
2672 },
2673 {
2674 .path = dir_s2d3,
2675 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2676 },
2677 {},
2678 };
2679 const int ruleset_fd = create_ruleset(
2680 _metadata,
2681 LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
2682
2683 ASSERT_LE(0, ruleset_fd);
2684 enforce_ruleset(_metadata, ruleset_fd);
2685 ASSERT_EQ(0, close(ruleset_fd));
2686
2687 ASSERT_EQ(0, unlink(file1_s1d1));
2688 ASSERT_EQ(0, unlink(file1_s1d2));
2689 ASSERT_EQ(0, unlink(file1_s1d3));
2690
2691 /* Denies linking because of missing MAKE_REG. */
2692 ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
2693 ASSERT_EQ(EACCES, errno);
2694 /* Denies linking because of missing source and destination REFER. */
2695 ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2));
2696 ASSERT_EQ(EXDEV, errno);
2697 /* Denies linking because of missing source REFER. */
2698 ASSERT_EQ(-1, link(file1_s2d1, file1_s1d3));
2699 ASSERT_EQ(EXDEV, errno);
2700
2701 /* Denies linking because of missing MAKE_REG. */
2702 ASSERT_EQ(-1, link(file1_s2d2, file1_s1d1));
2703 ASSERT_EQ(EACCES, errno);
2704 /* Denies linking because of missing destination REFER. */
2705 ASSERT_EQ(-1, link(file1_s2d2, file1_s1d2));
2706 ASSERT_EQ(EXDEV, errno);
2707
2708 /* Allows linking because of REFER and MAKE_REG. */
2709 ASSERT_EQ(0, link(file1_s2d2, file1_s1d3));
2710 ASSERT_EQ(0, unlink(file1_s2d2));
2711 /* Reverse linking denied because of missing MAKE_REG. */
2712 ASSERT_EQ(-1, link(file1_s1d3, file1_s2d2));
2713 ASSERT_EQ(EACCES, errno);
2714 ASSERT_EQ(0, unlink(file1_s2d3));
2715 /* Checks reverse linking. */
2716 ASSERT_EQ(0, link(file1_s1d3, file1_s2d3));
2717 ASSERT_EQ(0, unlink(file1_s1d3));
2718
2719 /*
2720 * This is OK for a file link, but it should not be allowed for a
2721 * directory rename (because of the superset of access rights.
2722 */
2723 ASSERT_EQ(0, link(file1_s2d3, file1_s1d3));
2724 ASSERT_EQ(0, unlink(file1_s1d3));
2725
2726 ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3));
2727 ASSERT_EQ(EXDEV, errno);
2728 ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2));
2729 ASSERT_EQ(EXDEV, errno);
2730
2731 ASSERT_EQ(0, link(file2_s1d2, file1_s1d2));
2732 ASSERT_EQ(0, link(file2_s1d3, file1_s1d3));
2733 }
2734
TEST_F_FORK(layout1,reparent_rename)2735 TEST_F_FORK(layout1, reparent_rename)
2736 {
2737 /* Same rules as for reparent_link. */
2738 const struct rule layer1[] = {
2739 {
2740 .path = dir_s1d2,
2741 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2742 },
2743 {
2744 .path = dir_s1d3,
2745 .access = LANDLOCK_ACCESS_FS_REFER,
2746 },
2747 {
2748 .path = dir_s2d2,
2749 .access = LANDLOCK_ACCESS_FS_REFER,
2750 },
2751 {
2752 .path = dir_s2d3,
2753 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2754 },
2755 {},
2756 };
2757 const int ruleset_fd = create_ruleset(
2758 _metadata,
2759 LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
2760
2761 ASSERT_LE(0, ruleset_fd);
2762 enforce_ruleset(_metadata, ruleset_fd);
2763 ASSERT_EQ(0, close(ruleset_fd));
2764
2765 ASSERT_EQ(0, unlink(file1_s1d2));
2766 ASSERT_EQ(0, unlink(file1_s1d3));
2767
2768 /* Denies renaming because of missing MAKE_REG. */
2769 ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s1d1,
2770 RENAME_EXCHANGE));
2771 ASSERT_EQ(EACCES, errno);
2772 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1,
2773 RENAME_EXCHANGE));
2774 ASSERT_EQ(EACCES, errno);
2775 ASSERT_EQ(0, unlink(file1_s1d1));
2776 ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1));
2777 ASSERT_EQ(EACCES, errno);
2778 /* Even denies same file exchange. */
2779 ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file2_s1d1,
2780 RENAME_EXCHANGE));
2781 ASSERT_EQ(EACCES, errno);
2782
2783 /* Denies renaming because of missing source and destination REFER. */
2784 ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d2));
2785 ASSERT_EQ(EXDEV, errno);
2786 /*
2787 * Denies renaming because of missing MAKE_REG, source and destination
2788 * REFER.
2789 */
2790 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d1,
2791 RENAME_EXCHANGE));
2792 ASSERT_EQ(EACCES, errno);
2793 ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s2d1,
2794 RENAME_EXCHANGE));
2795 ASSERT_EQ(EACCES, errno);
2796
2797 /* Denies renaming because of missing source REFER. */
2798 ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
2799 ASSERT_EQ(EXDEV, errno);
2800 /* Denies renaming because of missing MAKE_REG. */
2801 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d3,
2802 RENAME_EXCHANGE));
2803 ASSERT_EQ(EACCES, errno);
2804
2805 /* Denies renaming because of missing MAKE_REG. */
2806 ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d1));
2807 ASSERT_EQ(EACCES, errno);
2808 /* Denies renaming because of missing destination REFER*/
2809 ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2));
2810 ASSERT_EQ(EXDEV, errno);
2811
2812 /* Denies exchange because of one missing MAKE_REG. */
2813 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, file2_s1d3,
2814 RENAME_EXCHANGE));
2815 ASSERT_EQ(EACCES, errno);
2816 /* Allows renaming because of REFER and MAKE_REG. */
2817 ASSERT_EQ(0, rename(file1_s2d2, file1_s1d3));
2818
2819 /* Reverse renaming denied because of missing MAKE_REG. */
2820 ASSERT_EQ(-1, rename(file1_s1d3, file1_s2d2));
2821 ASSERT_EQ(EACCES, errno);
2822 ASSERT_EQ(0, unlink(file1_s2d3));
2823 ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
2824
2825 /* Tests reverse renaming. */
2826 ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
2827 ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s1d3,
2828 RENAME_EXCHANGE));
2829 ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
2830
2831 /*
2832 * This is OK for a file rename, but it should not be allowed for a
2833 * directory rename (because of the superset of access rights).
2834 */
2835 ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
2836 ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
2837
2838 /*
2839 * Tests superset restrictions applied to directories. Not only the
2840 * dir_s2d3's parent (dir_s2d2) should be taken into account but also
2841 * access rights tied to dir_s2d3. dir_s2d2 is missing one access right
2842 * compared to dir_s1d3/file1_s1d3 (MAKE_REG) but it is provided
2843 * directly by the moved dir_s2d3.
2844 */
2845 ASSERT_EQ(0, rename(dir_s2d3, file1_s1d3));
2846 ASSERT_EQ(0, rename(file1_s1d3, dir_s2d3));
2847 /*
2848 * The first rename is allowed but not the exchange because dir_s1d3's
2849 * parent (dir_s1d2) doesn't have REFER.
2850 */
2851 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3,
2852 RENAME_EXCHANGE));
2853 ASSERT_EQ(EXDEV, errno);
2854 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s2d3,
2855 RENAME_EXCHANGE));
2856 ASSERT_EQ(EXDEV, errno);
2857 ASSERT_EQ(-1, rename(file1_s2d3, dir_s1d3));
2858 ASSERT_EQ(EXDEV, errno);
2859
2860 ASSERT_EQ(-1, rename(file2_s1d2, file1_s1d3));
2861 ASSERT_EQ(EXDEV, errno);
2862 ASSERT_EQ(-1, rename(file2_s1d3, file1_s1d2));
2863 ASSERT_EQ(EXDEV, errno);
2864
2865 /* Renaming in the same directory is always allowed. */
2866 ASSERT_EQ(0, rename(file2_s1d2, file1_s1d2));
2867 ASSERT_EQ(0, rename(file2_s1d3, file1_s1d3));
2868
2869 ASSERT_EQ(0, unlink(file1_s1d2));
2870 /* Denies because of missing source MAKE_REG and destination REFER. */
2871 ASSERT_EQ(-1, rename(dir_s2d3, file1_s1d2));
2872 ASSERT_EQ(EXDEV, errno);
2873
2874 ASSERT_EQ(0, unlink(file1_s1d3));
2875 /* Denies because of missing source MAKE_REG and REFER. */
2876 ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d3));
2877 ASSERT_EQ(EXDEV, errno);
2878 }
2879
2880 static void
reparent_exdev_layers_enforce1(struct __test_metadata * const _metadata)2881 reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata)
2882 {
2883 const struct rule layer1[] = {
2884 {
2885 .path = dir_s1d2,
2886 .access = LANDLOCK_ACCESS_FS_REFER,
2887 },
2888 {
2889 /* Interesting for the layer2 tests. */
2890 .path = dir_s1d3,
2891 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2892 },
2893 {
2894 .path = dir_s2d2,
2895 .access = LANDLOCK_ACCESS_FS_REFER,
2896 },
2897 {
2898 .path = dir_s2d3,
2899 .access = LANDLOCK_ACCESS_FS_MAKE_REG,
2900 },
2901 {},
2902 };
2903 const int ruleset_fd = create_ruleset(
2904 _metadata,
2905 LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
2906
2907 ASSERT_LE(0, ruleset_fd);
2908 enforce_ruleset(_metadata, ruleset_fd);
2909 ASSERT_EQ(0, close(ruleset_fd));
2910 }
2911
2912 static void
reparent_exdev_layers_enforce2(struct __test_metadata * const _metadata)2913 reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata)
2914 {
2915 const struct rule layer2[] = {
2916 {
2917 .path = dir_s2d3,
2918 .access = LANDLOCK_ACCESS_FS_MAKE_DIR,
2919 },
2920 {},
2921 };
2922 /*
2923 * Same checks as before but with a second layer and a new MAKE_DIR
2924 * rule (and no explicit handling of REFER).
2925 */
2926 const int ruleset_fd =
2927 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2);
2928
2929 ASSERT_LE(0, ruleset_fd);
2930 enforce_ruleset(_metadata, ruleset_fd);
2931 ASSERT_EQ(0, close(ruleset_fd));
2932 }
2933
TEST_F_FORK(layout1,reparent_exdev_layers_rename1)2934 TEST_F_FORK(layout1, reparent_exdev_layers_rename1)
2935 {
2936 ASSERT_EQ(0, unlink(file1_s2d2));
2937 ASSERT_EQ(0, unlink(file1_s2d3));
2938
2939 reparent_exdev_layers_enforce1(_metadata);
2940
2941 /*
2942 * Moving the dir_s1d3 directory below dir_s2d2 is allowed by Landlock
2943 * because it doesn't inherit new access rights.
2944 */
2945 ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
2946 ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));
2947
2948 /*
2949 * Moving the dir_s1d3 directory below dir_s2d3 is allowed, even if it
2950 * gets a new inherited access rights (MAKE_REG), because MAKE_REG is
2951 * already allowed for dir_s1d3.
2952 */
2953 ASSERT_EQ(0, rename(dir_s1d3, file1_s2d3));
2954 ASSERT_EQ(0, rename(file1_s2d3, dir_s1d3));
2955
2956 /*
2957 * However, moving the file1_s1d3 file below dir_s2d3 is allowed
2958 * because it cannot inherit MAKE_REG right (which is dedicated to
2959 * directories).
2960 */
2961 ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
2962
2963 reparent_exdev_layers_enforce2(_metadata);
2964
2965 /*
2966 * Moving the dir_s1d3 directory below dir_s2d2 is now denied because
2967 * MAKE_DIR is not tied to dir_s2d2.
2968 */
2969 ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d2));
2970 ASSERT_EQ(EACCES, errno);
2971
2972 /*
2973 * Moving the dir_s1d3 directory below dir_s2d3 is forbidden because it
2974 * would grants MAKE_REG and MAKE_DIR rights to it.
2975 */
2976 ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
2977 ASSERT_EQ(EXDEV, errno);
2978
2979 /*
2980 * Moving the file2_s1d3 file below dir_s2d3 is denied because the
2981 * second layer does not handle REFER, which is always denied by
2982 * default.
2983 */
2984 ASSERT_EQ(-1, rename(file2_s1d3, file1_s2d3));
2985 ASSERT_EQ(EXDEV, errno);
2986 }
2987
TEST_F_FORK(layout1,reparent_exdev_layers_rename2)2988 TEST_F_FORK(layout1, reparent_exdev_layers_rename2)
2989 {
2990 reparent_exdev_layers_enforce1(_metadata);
2991
2992 /* Checks EACCES predominance over EXDEV. */
2993 ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
2994 ASSERT_EQ(EACCES, errno);
2995 ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d2));
2996 ASSERT_EQ(EACCES, errno);
2997 ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
2998 ASSERT_EQ(EXDEV, errno);
2999 /* Modify layout! */
3000 ASSERT_EQ(0, rename(file1_s1d2, file1_s2d3));
3001
3002 /* Without REFER source. */
3003 ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
3004 ASSERT_EQ(EXDEV, errno);
3005 ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
3006 ASSERT_EQ(EXDEV, errno);
3007
3008 reparent_exdev_layers_enforce2(_metadata);
3009
3010 /* Checks EACCES predominance over EXDEV. */
3011 ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
3012 ASSERT_EQ(EACCES, errno);
3013 /* Checks with actual file2_s1d2. */
3014 ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d2));
3015 ASSERT_EQ(EACCES, errno);
3016 ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
3017 ASSERT_EQ(EXDEV, errno);
3018 /*
3019 * Modifying the layout is now denied because the second layer does not
3020 * handle REFER, which is always denied by default.
3021 */
3022 ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
3023 ASSERT_EQ(EXDEV, errno);
3024
3025 /* Without REFER source, EACCES wins over EXDEV. */
3026 ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
3027 ASSERT_EQ(EACCES, errno);
3028 ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
3029 ASSERT_EQ(EACCES, errno);
3030 }
3031
TEST_F_FORK(layout1,reparent_exdev_layers_exchange1)3032 TEST_F_FORK(layout1, reparent_exdev_layers_exchange1)
3033 {
3034 const char *const dir_file1_s1d2 = file1_s1d2, *const dir_file2_s2d3 =
3035 file2_s2d3;
3036
3037 ASSERT_EQ(0, unlink(file1_s1d2));
3038 ASSERT_EQ(0, mkdir(file1_s1d2, 0700));
3039 ASSERT_EQ(0, unlink(file2_s2d3));
3040 ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
3041
3042 reparent_exdev_layers_enforce1(_metadata);
3043
3044 /* Error predominance with file exchange: returns EXDEV and EACCES. */
3045 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
3046 RENAME_EXCHANGE));
3047 ASSERT_EQ(EACCES, errno);
3048 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
3049 RENAME_EXCHANGE));
3050 ASSERT_EQ(EACCES, errno);
3051
3052 /*
3053 * Checks with directories which creation could be allowed, but denied
3054 * because of access rights that would be inherited.
3055 */
3056 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
3057 dir_file2_s2d3, RENAME_EXCHANGE));
3058 ASSERT_EQ(EXDEV, errno);
3059 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
3060 dir_file1_s1d2, RENAME_EXCHANGE));
3061 ASSERT_EQ(EXDEV, errno);
3062
3063 /* Checks with same access rights. */
3064 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
3065 RENAME_EXCHANGE));
3066 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
3067 RENAME_EXCHANGE));
3068
3069 /* Checks with different (child-only) access rights. */
3070 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
3071 RENAME_EXCHANGE));
3072 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
3073 RENAME_EXCHANGE));
3074
3075 /*
3076 * Checks that exchange between file and directory are consistent.
3077 *
3078 * Moving a file (file1_s2d2) to a directory which only grants more
3079 * directory-related access rights is allowed, and at the same time
3080 * moving a directory (dir_file2_s2d3) to another directory which
3081 * grants less access rights is allowed too.
3082 *
3083 * See layout1.reparent_exdev_layers_exchange3 for inverted arguments.
3084 */
3085 ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
3086 RENAME_EXCHANGE));
3087 /*
3088 * However, moving back the directory is denied because it would get
3089 * more access rights than the current state and because file creation
3090 * is forbidden (in dir_s2d2).
3091 */
3092 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
3093 RENAME_EXCHANGE));
3094 ASSERT_EQ(EACCES, errno);
3095 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
3096 RENAME_EXCHANGE));
3097 ASSERT_EQ(EACCES, errno);
3098
3099 reparent_exdev_layers_enforce2(_metadata);
3100
3101 /* Error predominance with file exchange: returns EXDEV and EACCES. */
3102 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
3103 RENAME_EXCHANGE));
3104 ASSERT_EQ(EACCES, errno);
3105 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
3106 RENAME_EXCHANGE));
3107 ASSERT_EQ(EACCES, errno);
3108
3109 /* Checks with directories which creation is now denied. */
3110 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
3111 dir_file2_s2d3, RENAME_EXCHANGE));
3112 ASSERT_EQ(EACCES, errno);
3113 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
3114 dir_file1_s1d2, RENAME_EXCHANGE));
3115 ASSERT_EQ(EACCES, errno);
3116
3117 /* Checks with different (child-only) access rights. */
3118 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
3119 RENAME_EXCHANGE));
3120 /* Denied because of MAKE_DIR. */
3121 ASSERT_EQ(EACCES, errno);
3122 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
3123 RENAME_EXCHANGE));
3124 ASSERT_EQ(EACCES, errno);
3125
3126 /* Checks with different (child-only) access rights. */
3127 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
3128 RENAME_EXCHANGE));
3129 /* Denied because of MAKE_DIR. */
3130 ASSERT_EQ(EACCES, errno);
3131 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
3132 RENAME_EXCHANGE));
3133 ASSERT_EQ(EACCES, errno);
3134
3135 /* See layout1.reparent_exdev_layers_exchange2 for complement. */
3136 }
3137
TEST_F_FORK(layout1,reparent_exdev_layers_exchange2)3138 TEST_F_FORK(layout1, reparent_exdev_layers_exchange2)
3139 {
3140 const char *const dir_file2_s2d3 = file2_s2d3;
3141
3142 ASSERT_EQ(0, unlink(file2_s2d3));
3143 ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
3144
3145 reparent_exdev_layers_enforce1(_metadata);
3146 reparent_exdev_layers_enforce2(_metadata);
3147
3148 /* Checks that exchange between file and directory are consistent. */
3149 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
3150 RENAME_EXCHANGE));
3151 ASSERT_EQ(EACCES, errno);
3152 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
3153 RENAME_EXCHANGE));
3154 ASSERT_EQ(EACCES, errno);
3155 }
3156
TEST_F_FORK(layout1,reparent_exdev_layers_exchange3)3157 TEST_F_FORK(layout1, reparent_exdev_layers_exchange3)
3158 {
3159 const char *const dir_file2_s2d3 = file2_s2d3;
3160
3161 ASSERT_EQ(0, unlink(file2_s2d3));
3162 ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
3163
3164 reparent_exdev_layers_enforce1(_metadata);
3165
3166 /*
3167 * Checks that exchange between file and directory are consistent,
3168 * including with inverted arguments (see
3169 * layout1.reparent_exdev_layers_exchange1).
3170 */
3171 ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
3172 RENAME_EXCHANGE));
3173 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
3174 RENAME_EXCHANGE));
3175 ASSERT_EQ(EACCES, errno);
3176 ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
3177 RENAME_EXCHANGE));
3178 ASSERT_EQ(EACCES, errno);
3179 }
3180
TEST_F_FORK(layout1,reparent_remove)3181 TEST_F_FORK(layout1, reparent_remove)
3182 {
3183 const struct rule layer1[] = {
3184 {
3185 .path = dir_s1d1,
3186 .access = LANDLOCK_ACCESS_FS_REFER |
3187 LANDLOCK_ACCESS_FS_REMOVE_DIR,
3188 },
3189 {
3190 .path = dir_s1d2,
3191 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
3192 },
3193 {
3194 .path = dir_s2d1,
3195 .access = LANDLOCK_ACCESS_FS_REFER |
3196 LANDLOCK_ACCESS_FS_REMOVE_FILE,
3197 },
3198 {},
3199 };
3200 const int ruleset_fd = create_ruleset(
3201 _metadata,
3202 LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR |
3203 LANDLOCK_ACCESS_FS_REMOVE_FILE,
3204 layer1);
3205
3206 ASSERT_LE(0, ruleset_fd);
3207 enforce_ruleset(_metadata, ruleset_fd);
3208 ASSERT_EQ(0, close(ruleset_fd));
3209
3210 /* Access denied because of wrong/swapped remove file/dir. */
3211 ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2));
3212 ASSERT_EQ(EACCES, errno);
3213 ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d1));
3214 ASSERT_EQ(EACCES, errno);
3215 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d2,
3216 RENAME_EXCHANGE));
3217 ASSERT_EQ(EACCES, errno);
3218 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d3,
3219 RENAME_EXCHANGE));
3220 ASSERT_EQ(EACCES, errno);
3221
3222 /* Access allowed thanks to the matching rights. */
3223 ASSERT_EQ(-1, rename(file1_s2d1, dir_s1d2));
3224 ASSERT_EQ(EISDIR, errno);
3225 ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d1));
3226 ASSERT_EQ(ENOTDIR, errno);
3227 ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
3228 ASSERT_EQ(ENOTDIR, errno);
3229 ASSERT_EQ(0, unlink(file1_s2d1));
3230 ASSERT_EQ(0, unlink(file1_s1d3));
3231 ASSERT_EQ(0, unlink(file2_s1d3));
3232 ASSERT_EQ(0, rename(dir_s1d3, file1_s2d1));
3233
3234 /* Effectively removes a file and a directory by exchanging them. */
3235 ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
3236 ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
3237 RENAME_EXCHANGE));
3238 ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
3239 RENAME_EXCHANGE));
3240 ASSERT_EQ(EACCES, errno);
3241 }
3242
TEST_F_FORK(layout1,reparent_dom_superset)3243 TEST_F_FORK(layout1, reparent_dom_superset)
3244 {
3245 const struct rule layer1[] = {
3246 {
3247 .path = dir_s1d2,
3248 .access = LANDLOCK_ACCESS_FS_REFER,
3249 },
3250 {
3251 .path = file1_s1d2,
3252 .access = LANDLOCK_ACCESS_FS_EXECUTE,
3253 },
3254 {
3255 .path = dir_s1d3,
3256 .access = LANDLOCK_ACCESS_FS_MAKE_SOCK |
3257 LANDLOCK_ACCESS_FS_EXECUTE,
3258 },
3259 {
3260 .path = dir_s2d2,
3261 .access = LANDLOCK_ACCESS_FS_REFER |
3262 LANDLOCK_ACCESS_FS_EXECUTE |
3263 LANDLOCK_ACCESS_FS_MAKE_SOCK,
3264 },
3265 {
3266 .path = dir_s2d3,
3267 .access = LANDLOCK_ACCESS_FS_READ_FILE |
3268 LANDLOCK_ACCESS_FS_MAKE_FIFO,
3269 },
3270 {},
3271 };
3272 int ruleset_fd = create_ruleset(_metadata,
3273 LANDLOCK_ACCESS_FS_REFER |
3274 LANDLOCK_ACCESS_FS_EXECUTE |
3275 LANDLOCK_ACCESS_FS_MAKE_SOCK |
3276 LANDLOCK_ACCESS_FS_READ_FILE |
3277 LANDLOCK_ACCESS_FS_MAKE_FIFO,
3278 layer1);
3279
3280 ASSERT_LE(0, ruleset_fd);
3281 enforce_ruleset(_metadata, ruleset_fd);
3282 ASSERT_EQ(0, close(ruleset_fd));
3283
3284 ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1));
3285 ASSERT_EQ(EXDEV, errno);
3286 /*
3287 * Moving file1_s1d2 beneath dir_s2d3 would grant it the READ_FILE
3288 * access right.
3289 */
3290 ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d3));
3291 ASSERT_EQ(EXDEV, errno);
3292 /*
3293 * Moving file1_s1d2 should be allowed even if dir_s2d2 grants a
3294 * superset of access rights compared to dir_s1d2, because file1_s1d2
3295 * already has these access rights anyway.
3296 */
3297 ASSERT_EQ(0, rename(file1_s1d2, file1_s2d2));
3298 ASSERT_EQ(0, rename(file1_s2d2, file1_s1d2));
3299
3300 ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
3301 ASSERT_EQ(EXDEV, errno);
3302 /*
3303 * Moving dir_s1d3 beneath dir_s2d3 would grant it the MAKE_FIFO access
3304 * right.
3305 */
3306 ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
3307 ASSERT_EQ(EXDEV, errno);
3308 /*
3309 * Moving dir_s1d3 should be allowed even if dir_s2d2 grants a superset
3310 * of access rights compared to dir_s1d2, because dir_s1d3 already has
3311 * these access rights anyway.
3312 */
3313 ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
3314 ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));
3315
3316 /*
3317 * Moving file1_s2d3 beneath dir_s1d2 is allowed, but moving it back
3318 * will be denied because the new inherited access rights from dir_s1d2
3319 * will be less than the destination (original) dir_s2d3. This is a
3320 * sinkhole scenario where we cannot move back files or directories.
3321 */
3322 ASSERT_EQ(0, rename(file1_s2d3, file2_s1d2));
3323 ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
3324 ASSERT_EQ(EXDEV, errno);
3325 ASSERT_EQ(0, unlink(file2_s1d2));
3326 ASSERT_EQ(0, unlink(file2_s2d3));
3327 /*
3328 * Checks similar directory one-way move: dir_s2d3 loses EXECUTE and
3329 * MAKE_SOCK which were inherited from dir_s1d3.
3330 */
3331 ASSERT_EQ(0, rename(dir_s2d3, file2_s1d2));
3332 ASSERT_EQ(-1, rename(file2_s1d2, dir_s2d3));
3333 ASSERT_EQ(EXDEV, errno);
3334 }
3335
TEST_F_FORK(layout1,remove_dir)3336 TEST_F_FORK(layout1, remove_dir)
3337 {
3338 const struct rule rules[] = {
3339 {
3340 .path = dir_s1d2,
3341 .access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
3342 },
3343 {},
3344 };
3345 const int ruleset_fd =
3346 create_ruleset(_metadata, rules[0].access, rules);
3347
3348 ASSERT_LE(0, ruleset_fd);
3349
3350 ASSERT_EQ(0, unlink(file1_s1d1));
3351 ASSERT_EQ(0, unlink(file1_s1d2));
3352 ASSERT_EQ(0, unlink(file1_s1d3));
3353 ASSERT_EQ(0, unlink(file2_s1d3));
3354
3355 enforce_ruleset(_metadata, ruleset_fd);
3356 ASSERT_EQ(0, close(ruleset_fd));
3357
3358 ASSERT_EQ(0, rmdir(dir_s1d3));
3359 ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
3360 ASSERT_EQ(0, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR));
3361
3362 /* dir_s1d2 itself cannot be removed. */
3363 ASSERT_EQ(-1, rmdir(dir_s1d2));
3364 ASSERT_EQ(EACCES, errno);
3365 ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d2, AT_REMOVEDIR));
3366 ASSERT_EQ(EACCES, errno);
3367 ASSERT_EQ(-1, rmdir(dir_s1d1));
3368 ASSERT_EQ(EACCES, errno);
3369 ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d1, AT_REMOVEDIR));
3370 ASSERT_EQ(EACCES, errno);
3371 }
3372
TEST_F_FORK(layout1,remove_file)3373 TEST_F_FORK(layout1, remove_file)
3374 {
3375 const struct rule rules[] = {
3376 {
3377 .path = dir_s1d2,
3378 .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
3379 },
3380 {},
3381 };
3382 const int ruleset_fd =
3383 create_ruleset(_metadata, rules[0].access, rules);
3384
3385 ASSERT_LE(0, ruleset_fd);
3386 enforce_ruleset(_metadata, ruleset_fd);
3387 ASSERT_EQ(0, close(ruleset_fd));
3388
3389 ASSERT_EQ(-1, unlink(file1_s1d1));
3390 ASSERT_EQ(EACCES, errno);
3391 ASSERT_EQ(-1, unlinkat(AT_FDCWD, file1_s1d1, 0));
3392 ASSERT_EQ(EACCES, errno);
3393 ASSERT_EQ(0, unlink(file1_s1d2));
3394 ASSERT_EQ(0, unlinkat(AT_FDCWD, file1_s1d3, 0));
3395 }
3396
test_make_file(struct __test_metadata * const _metadata,const __u64 access,const mode_t mode,const dev_t dev)3397 static void test_make_file(struct __test_metadata *const _metadata,
3398 const __u64 access, const mode_t mode,
3399 const dev_t dev)
3400 {
3401 const struct rule rules[] = {
3402 {
3403 .path = dir_s1d2,
3404 .access = access,
3405 },
3406 {},
3407 };
3408 const int ruleset_fd = create_ruleset(_metadata, access, rules);
3409
3410 ASSERT_LE(0, ruleset_fd);
3411
3412 ASSERT_EQ(0, unlink(file1_s1d1));
3413 ASSERT_EQ(0, unlink(file2_s1d1));
3414 ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev))
3415 {
3416 TH_LOG("Failed to make file \"%s\": %s", file2_s1d1,
3417 strerror(errno));
3418 };
3419
3420 ASSERT_EQ(0, unlink(file1_s1d2));
3421 ASSERT_EQ(0, unlink(file2_s1d2));
3422
3423 ASSERT_EQ(0, unlink(file1_s1d3));
3424 ASSERT_EQ(0, unlink(file2_s1d3));
3425
3426 enforce_ruleset(_metadata, ruleset_fd);
3427 ASSERT_EQ(0, close(ruleset_fd));
3428
3429 ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev));
3430 ASSERT_EQ(EACCES, errno);
3431 ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
3432 ASSERT_EQ(EACCES, errno);
3433 ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1));
3434 ASSERT_EQ(EACCES, errno);
3435
3436 ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev))
3437 {
3438 TH_LOG("Failed to make file \"%s\": %s", file1_s1d2,
3439 strerror(errno));
3440 };
3441 ASSERT_EQ(0, link(file1_s1d2, file2_s1d2));
3442 ASSERT_EQ(0, unlink(file2_s1d2));
3443 ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2));
3444
3445 ASSERT_EQ(0, mknod(file1_s1d3, mode | 0400, dev));
3446 ASSERT_EQ(0, link(file1_s1d3, file2_s1d3));
3447 ASSERT_EQ(0, unlink(file2_s1d3));
3448 ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3));
3449 }
3450
TEST_F_FORK(layout1,make_char)3451 TEST_F_FORK(layout1, make_char)
3452 {
3453 /* Creates a /dev/null device. */
3454 set_cap(_metadata, CAP_MKNOD);
3455 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR,
3456 makedev(1, 3));
3457 }
3458
TEST_F_FORK(layout1,make_block)3459 TEST_F_FORK(layout1, make_block)
3460 {
3461 /* Creates a /dev/loop0 device. */
3462 set_cap(_metadata, CAP_MKNOD);
3463 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK,
3464 makedev(7, 0));
3465 }
3466
TEST_F_FORK(layout1,make_reg_1)3467 TEST_F_FORK(layout1, make_reg_1)
3468 {
3469 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFREG, 0);
3470 }
3471
TEST_F_FORK(layout1,make_reg_2)3472 TEST_F_FORK(layout1, make_reg_2)
3473 {
3474 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, 0, 0);
3475 }
3476
TEST_F_FORK(layout1,make_sock)3477 TEST_F_FORK(layout1, make_sock)
3478 {
3479 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_SOCK, S_IFSOCK, 0);
3480 }
3481
TEST_F_FORK(layout1,make_fifo)3482 TEST_F_FORK(layout1, make_fifo)
3483 {
3484 test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_FIFO, S_IFIFO, 0);
3485 }
3486
TEST_F_FORK(layout1,make_sym)3487 TEST_F_FORK(layout1, make_sym)
3488 {
3489 const struct rule rules[] = {
3490 {
3491 .path = dir_s1d2,
3492 .access = LANDLOCK_ACCESS_FS_MAKE_SYM,
3493 },
3494 {},
3495 };
3496 const int ruleset_fd =
3497 create_ruleset(_metadata, rules[0].access, rules);
3498
3499 ASSERT_LE(0, ruleset_fd);
3500
3501 ASSERT_EQ(0, unlink(file1_s1d1));
3502 ASSERT_EQ(0, unlink(file2_s1d1));
3503 ASSERT_EQ(0, symlink("none", file2_s1d1));
3504
3505 ASSERT_EQ(0, unlink(file1_s1d2));
3506 ASSERT_EQ(0, unlink(file2_s1d2));
3507
3508 ASSERT_EQ(0, unlink(file1_s1d3));
3509 ASSERT_EQ(0, unlink(file2_s1d3));
3510
3511 enforce_ruleset(_metadata, ruleset_fd);
3512 ASSERT_EQ(0, close(ruleset_fd));
3513
3514 ASSERT_EQ(-1, symlink("none", file1_s1d1));
3515 ASSERT_EQ(EACCES, errno);
3516 ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
3517 ASSERT_EQ(EACCES, errno);
3518 ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1));
3519 ASSERT_EQ(EACCES, errno);
3520
3521 ASSERT_EQ(0, symlink("none", file1_s1d2));
3522 ASSERT_EQ(0, link(file1_s1d2, file2_s1d2));
3523 ASSERT_EQ(0, unlink(file2_s1d2));
3524 ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2));
3525
3526 ASSERT_EQ(0, symlink("none", file1_s1d3));
3527 ASSERT_EQ(0, link(file1_s1d3, file2_s1d3));
3528 ASSERT_EQ(0, unlink(file2_s1d3));
3529 ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3));
3530 }
3531
TEST_F_FORK(layout1,make_dir)3532 TEST_F_FORK(layout1, make_dir)
3533 {
3534 const struct rule rules[] = {
3535 {
3536 .path = dir_s1d2,
3537 .access = LANDLOCK_ACCESS_FS_MAKE_DIR,
3538 },
3539 {},
3540 };
3541 const int ruleset_fd =
3542 create_ruleset(_metadata, rules[0].access, rules);
3543
3544 ASSERT_LE(0, ruleset_fd);
3545
3546 ASSERT_EQ(0, unlink(file1_s1d1));
3547 ASSERT_EQ(0, unlink(file1_s1d2));
3548 ASSERT_EQ(0, unlink(file1_s1d3));
3549
3550 enforce_ruleset(_metadata, ruleset_fd);
3551 ASSERT_EQ(0, close(ruleset_fd));
3552
3553 /* Uses file_* as directory names. */
3554 ASSERT_EQ(-1, mkdir(file1_s1d1, 0700));
3555 ASSERT_EQ(EACCES, errno);
3556 ASSERT_EQ(0, mkdir(file1_s1d2, 0700));
3557 ASSERT_EQ(0, mkdir(file1_s1d3, 0700));
3558 }
3559
open_proc_fd(struct __test_metadata * const _metadata,const int fd,const int open_flags)3560 static int open_proc_fd(struct __test_metadata *const _metadata, const int fd,
3561 const int open_flags)
3562 {
3563 static const char path_template[] = "/proc/self/fd/%d";
3564 char procfd_path[sizeof(path_template) + 10];
3565 const int procfd_path_size =
3566 snprintf(procfd_path, sizeof(procfd_path), path_template, fd);
3567
3568 ASSERT_LT(procfd_path_size, sizeof(procfd_path));
3569 return open(procfd_path, open_flags);
3570 }
3571
TEST_F_FORK(layout1,proc_unlinked_file)3572 TEST_F_FORK(layout1, proc_unlinked_file)
3573 {
3574 const struct rule rules[] = {
3575 {
3576 .path = file1_s1d2,
3577 .access = LANDLOCK_ACCESS_FS_READ_FILE,
3578 },
3579 {},
3580 };
3581 int reg_fd, proc_fd;
3582 const int ruleset_fd = create_ruleset(
3583 _metadata,
3584 LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
3585 rules);
3586
3587 ASSERT_LE(0, ruleset_fd);
3588 enforce_ruleset(_metadata, ruleset_fd);
3589 ASSERT_EQ(0, close(ruleset_fd));
3590
3591 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
3592 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
3593 reg_fd = open(file1_s1d2, O_RDONLY | O_CLOEXEC);
3594 ASSERT_LE(0, reg_fd);
3595 ASSERT_EQ(0, unlink(file1_s1d2));
3596
3597 proc_fd = open_proc_fd(_metadata, reg_fd, O_RDONLY | O_CLOEXEC);
3598 ASSERT_LE(0, proc_fd);
3599 ASSERT_EQ(0, close(proc_fd));
3600
3601 proc_fd = open_proc_fd(_metadata, reg_fd, O_RDWR | O_CLOEXEC);
3602 ASSERT_EQ(-1, proc_fd)
3603 {
3604 TH_LOG("Successfully opened /proc/self/fd/%d: %s", reg_fd,
3605 strerror(errno));
3606 }
3607 ASSERT_EQ(EACCES, errno);
3608
3609 ASSERT_EQ(0, close(reg_fd));
3610 }
3611
TEST_F_FORK(layout1,proc_pipe)3612 TEST_F_FORK(layout1, proc_pipe)
3613 {
3614 int proc_fd;
3615 int pipe_fds[2];
3616 char buf = '\0';
3617 const struct rule rules[] = {
3618 {
3619 .path = dir_s1d2,
3620 .access = LANDLOCK_ACCESS_FS_READ_FILE |
3621 LANDLOCK_ACCESS_FS_WRITE_FILE,
3622 },
3623 {},
3624 };
3625 /* Limits read and write access to files tied to the filesystem. */
3626 const int ruleset_fd =
3627 create_ruleset(_metadata, rules[0].access, rules);
3628
3629 ASSERT_LE(0, ruleset_fd);
3630 enforce_ruleset(_metadata, ruleset_fd);
3631 ASSERT_EQ(0, close(ruleset_fd));
3632
3633 /* Checks enforcement for normal files. */
3634 ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR));
3635 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR));
3636
3637 /* Checks access to pipes through FD. */
3638 ASSERT_EQ(0, pipe2(pipe_fds, O_CLOEXEC));
3639 ASSERT_EQ(1, write(pipe_fds[1], ".", 1))
3640 {
3641 TH_LOG("Failed to write in pipe: %s", strerror(errno));
3642 }
3643 ASSERT_EQ(1, read(pipe_fds[0], &buf, 1));
3644 ASSERT_EQ('.', buf);
3645
3646 /* Checks write access to pipe through /proc/self/fd . */
3647 proc_fd = open_proc_fd(_metadata, pipe_fds[1], O_WRONLY | O_CLOEXEC);
3648 ASSERT_LE(0, proc_fd);
3649 ASSERT_EQ(1, write(proc_fd, ".", 1))
3650 {
3651 TH_LOG("Failed to write through /proc/self/fd/%d: %s",
3652 pipe_fds[1], strerror(errno));
3653 }
3654 ASSERT_EQ(0, close(proc_fd));
3655
3656 /* Checks read access to pipe through /proc/self/fd . */
3657 proc_fd = open_proc_fd(_metadata, pipe_fds[0], O_RDONLY | O_CLOEXEC);
3658 ASSERT_LE(0, proc_fd);
3659 buf = '\0';
3660 ASSERT_EQ(1, read(proc_fd, &buf, 1))
3661 {
3662 TH_LOG("Failed to read through /proc/self/fd/%d: %s",
3663 pipe_fds[1], strerror(errno));
3664 }
3665 ASSERT_EQ(0, close(proc_fd));
3666
3667 ASSERT_EQ(0, close(pipe_fds[0]));
3668 ASSERT_EQ(0, close(pipe_fds[1]));
3669 }
3670
3671 /* Invokes truncate(2) and returns its errno or 0. */
test_truncate(const char * const path)3672 static int test_truncate(const char *const path)
3673 {
3674 if (truncate(path, 10) < 0)
3675 return errno;
3676 return 0;
3677 }
3678
3679 /*
3680 * Invokes creat(2) and returns its errno or 0.
3681 * Closes the opened file descriptor on success.
3682 */
test_creat(const char * const path)3683 static int test_creat(const char *const path)
3684 {
3685 int fd = creat(path, 0600);
3686
3687 if (fd < 0)
3688 return errno;
3689
3690 /*
3691 * Mixing error codes from close(2) and creat(2) should not lead to any
3692 * (access type) confusion for this test.
3693 */
3694 if (close(fd) < 0)
3695 return errno;
3696 return 0;
3697 }
3698
3699 /*
3700 * Exercises file truncation when it's not restricted,
3701 * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed.
3702 */
TEST_F_FORK(layout1,truncate_unhandled)3703 TEST_F_FORK(layout1, truncate_unhandled)
3704 {
3705 const char *const file_r = file1_s1d1;
3706 const char *const file_w = file2_s1d1;
3707 const char *const file_none = file1_s1d2;
3708 const struct rule rules[] = {
3709 {
3710 .path = file_r,
3711 .access = LANDLOCK_ACCESS_FS_READ_FILE,
3712 },
3713 {
3714 .path = file_w,
3715 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
3716 },
3717 /* Implicitly: No rights for file_none. */
3718 {},
3719 };
3720
3721 const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
3722 LANDLOCK_ACCESS_FS_WRITE_FILE;
3723 int ruleset_fd;
3724
3725 /* Enables Landlock. */
3726 ruleset_fd = create_ruleset(_metadata, handled, rules);
3727
3728 ASSERT_LE(0, ruleset_fd);
3729 enforce_ruleset(_metadata, ruleset_fd);
3730 ASSERT_EQ(0, close(ruleset_fd));
3731
3732 /*
3733 * Checks read right: truncate and open with O_TRUNC work, unless the
3734 * file is attempted to be opened for writing.
3735 */
3736 EXPECT_EQ(0, test_truncate(file_r));
3737 EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC));
3738 EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC));
3739 EXPECT_EQ(EACCES, test_creat(file_r));
3740
3741 /*
3742 * Checks write right: truncate and open with O_TRUNC work, unless the
3743 * file is attempted to be opened for reading.
3744 */
3745 EXPECT_EQ(0, test_truncate(file_w));
3746 EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC));
3747 EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC));
3748 EXPECT_EQ(0, test_creat(file_w));
3749
3750 /*
3751 * Checks "no rights" case: truncate works but all open attempts fail,
3752 * including creat.
3753 */
3754 EXPECT_EQ(0, test_truncate(file_none));
3755 EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC));
3756 EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC));
3757 EXPECT_EQ(EACCES, test_creat(file_none));
3758 }
3759
TEST_F_FORK(layout1,truncate)3760 TEST_F_FORK(layout1, truncate)
3761 {
3762 const char *const file_rwt = file1_s1d1;
3763 const char *const file_rw = file2_s1d1;
3764 const char *const file_rt = file1_s1d2;
3765 const char *const file_t = file2_s1d2;
3766 const char *const file_none = file1_s1d3;
3767 const char *const dir_t = dir_s2d1;
3768 const char *const file_in_dir_t = file1_s2d1;
3769 const char *const dir_w = dir_s3d1;
3770 const char *const file_in_dir_w = file1_s3d1;
3771 const struct rule rules[] = {
3772 {
3773 .path = file_rwt,
3774 .access = LANDLOCK_ACCESS_FS_READ_FILE |
3775 LANDLOCK_ACCESS_FS_WRITE_FILE |
3776 LANDLOCK_ACCESS_FS_TRUNCATE,
3777 },
3778 {
3779 .path = file_rw,
3780 .access = LANDLOCK_ACCESS_FS_READ_FILE |
3781 LANDLOCK_ACCESS_FS_WRITE_FILE,
3782 },
3783 {
3784 .path = file_rt,
3785 .access = LANDLOCK_ACCESS_FS_READ_FILE |
3786 LANDLOCK_ACCESS_FS_TRUNCATE,
3787 },
3788 {
3789 .path = file_t,
3790 .access = LANDLOCK_ACCESS_FS_TRUNCATE,
3791 },
3792 /* Implicitly: No access rights for file_none. */
3793 {
3794 .path = dir_t,
3795 .access = LANDLOCK_ACCESS_FS_TRUNCATE,
3796 },
3797 {
3798 .path = dir_w,
3799 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
3800 },
3801 {},
3802 };
3803 const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
3804 LANDLOCK_ACCESS_FS_WRITE_FILE |
3805 LANDLOCK_ACCESS_FS_TRUNCATE;
3806 int ruleset_fd;
3807
3808 /* Enables Landlock. */
3809 ruleset_fd = create_ruleset(_metadata, handled, rules);
3810
3811 ASSERT_LE(0, ruleset_fd);
3812 enforce_ruleset(_metadata, ruleset_fd);
3813 ASSERT_EQ(0, close(ruleset_fd));
3814
3815 /* Checks read, write and truncate rights: truncation works. */
3816 EXPECT_EQ(0, test_truncate(file_rwt));
3817 EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC));
3818 EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC));
3819
3820 /* Checks read and write rights: no truncate variant works. */
3821 EXPECT_EQ(EACCES, test_truncate(file_rw));
3822 EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC));
3823 EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC));
3824
3825 /*
3826 * Checks read and truncate rights: truncation works.
3827 *
3828 * Note: Files can get truncated using open() even with O_RDONLY.
3829 */
3830 EXPECT_EQ(0, test_truncate(file_rt));
3831 EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC));
3832 EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC));
3833
3834 /* Checks truncate right: truncate works, but can't open file. */
3835 EXPECT_EQ(0, test_truncate(file_t));
3836 EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC));
3837 EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC));
3838
3839 /* Checks "no rights" case: No form of truncation works. */
3840 EXPECT_EQ(EACCES, test_truncate(file_none));
3841 EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC));
3842 EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC));
3843
3844 /*
3845 * Checks truncate right on directory: truncate works on contained
3846 * files.
3847 */
3848 EXPECT_EQ(0, test_truncate(file_in_dir_t));
3849 EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC));
3850 EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC));
3851
3852 /*
3853 * Checks creat in dir_w: This requires the truncate right when
3854 * overwriting an existing file, but does not require it when the file
3855 * is new.
3856 */
3857 EXPECT_EQ(EACCES, test_creat(file_in_dir_w));
3858
3859 ASSERT_EQ(0, unlink(file_in_dir_w));
3860 EXPECT_EQ(0, test_creat(file_in_dir_w));
3861 }
3862
3863 /* Invokes ftruncate(2) and returns its errno or 0. */
test_ftruncate(int fd)3864 static int test_ftruncate(int fd)
3865 {
3866 if (ftruncate(fd, 10) < 0)
3867 return errno;
3868 return 0;
3869 }
3870
TEST_F_FORK(layout1,ftruncate)3871 TEST_F_FORK(layout1, ftruncate)
3872 {
3873 /*
3874 * This test opens a new file descriptor at different stages of
3875 * Landlock restriction:
3876 *
3877 * without restriction: ftruncate works
3878 * something else but truncate restricted: ftruncate works
3879 * truncate restricted and permitted: ftruncate works
3880 * truncate restricted and not permitted: ftruncate fails
3881 *
3882 * Whether this works or not is expected to depend on the time when the
3883 * FD was opened, not to depend on the time when ftruncate() was
3884 * called.
3885 */
3886 const char *const path = file1_s1d1;
3887 const __u64 handled1 = LANDLOCK_ACCESS_FS_READ_FILE |
3888 LANDLOCK_ACCESS_FS_WRITE_FILE;
3889 const struct rule layer1[] = {
3890 {
3891 .path = path,
3892 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
3893 },
3894 {},
3895 };
3896 const __u64 handled2 = LANDLOCK_ACCESS_FS_TRUNCATE;
3897 const struct rule layer2[] = {
3898 {
3899 .path = path,
3900 .access = LANDLOCK_ACCESS_FS_TRUNCATE,
3901 },
3902 {},
3903 };
3904 const __u64 handled3 = LANDLOCK_ACCESS_FS_TRUNCATE |
3905 LANDLOCK_ACCESS_FS_WRITE_FILE;
3906 const struct rule layer3[] = {
3907 {
3908 .path = path,
3909 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
3910 },
3911 {},
3912 };
3913 int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd;
3914
3915 fd_layer0 = open(path, O_WRONLY);
3916 EXPECT_EQ(0, test_ftruncate(fd_layer0));
3917
3918 ruleset_fd = create_ruleset(_metadata, handled1, layer1);
3919 ASSERT_LE(0, ruleset_fd);
3920 enforce_ruleset(_metadata, ruleset_fd);
3921 ASSERT_EQ(0, close(ruleset_fd));
3922
3923 fd_layer1 = open(path, O_WRONLY);
3924 EXPECT_EQ(0, test_ftruncate(fd_layer0));
3925 EXPECT_EQ(0, test_ftruncate(fd_layer1));
3926
3927 ruleset_fd = create_ruleset(_metadata, handled2, layer2);
3928 ASSERT_LE(0, ruleset_fd);
3929 enforce_ruleset(_metadata, ruleset_fd);
3930 ASSERT_EQ(0, close(ruleset_fd));
3931
3932 fd_layer2 = open(path, O_WRONLY);
3933 EXPECT_EQ(0, test_ftruncate(fd_layer0));
3934 EXPECT_EQ(0, test_ftruncate(fd_layer1));
3935 EXPECT_EQ(0, test_ftruncate(fd_layer2));
3936
3937 ruleset_fd = create_ruleset(_metadata, handled3, layer3);
3938 ASSERT_LE(0, ruleset_fd);
3939 enforce_ruleset(_metadata, ruleset_fd);
3940 ASSERT_EQ(0, close(ruleset_fd));
3941
3942 fd_layer3 = open(path, O_WRONLY);
3943 EXPECT_EQ(0, test_ftruncate(fd_layer0));
3944 EXPECT_EQ(0, test_ftruncate(fd_layer1));
3945 EXPECT_EQ(0, test_ftruncate(fd_layer2));
3946 EXPECT_EQ(EACCES, test_ftruncate(fd_layer3));
3947
3948 ASSERT_EQ(0, close(fd_layer0));
3949 ASSERT_EQ(0, close(fd_layer1));
3950 ASSERT_EQ(0, close(fd_layer2));
3951 ASSERT_EQ(0, close(fd_layer3));
3952 }
3953
3954 /* clang-format off */
FIXTURE(ftruncate)3955 FIXTURE(ftruncate) {};
3956 /* clang-format on */
3957
FIXTURE_SETUP(ftruncate)3958 FIXTURE_SETUP(ftruncate)
3959 {
3960 prepare_layout(_metadata);
3961 create_file(_metadata, file1_s1d1);
3962 }
3963
FIXTURE_TEARDOWN_PARENT(ftruncate)3964 FIXTURE_TEARDOWN_PARENT(ftruncate)
3965 {
3966 EXPECT_EQ(0, remove_path(file1_s1d1));
3967 cleanup_layout(_metadata);
3968 }
3969
FIXTURE_VARIANT(ftruncate)3970 FIXTURE_VARIANT(ftruncate)
3971 {
3972 const __u64 handled;
3973 const __u64 allowed;
3974 const int expected_open_result;
3975 const int expected_ftruncate_result;
3976 };
3977
3978 /* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate,w_w)3979 FIXTURE_VARIANT_ADD(ftruncate, w_w) {
3980 /* clang-format on */
3981 .handled = LANDLOCK_ACCESS_FS_WRITE_FILE,
3982 .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE,
3983 .expected_open_result = 0,
3984 .expected_ftruncate_result = 0,
3985 };
3986
3987 /* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate,t_t)3988 FIXTURE_VARIANT_ADD(ftruncate, t_t) {
3989 /* clang-format on */
3990 .handled = LANDLOCK_ACCESS_FS_TRUNCATE,
3991 .allowed = LANDLOCK_ACCESS_FS_TRUNCATE,
3992 .expected_open_result = 0,
3993 .expected_ftruncate_result = 0,
3994 };
3995
3996 /* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate,wt_w)3997 FIXTURE_VARIANT_ADD(ftruncate, wt_w) {
3998 /* clang-format on */
3999 .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
4000 .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE,
4001 .expected_open_result = 0,
4002 .expected_ftruncate_result = EACCES,
4003 };
4004
4005 /* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate,wt_wt)4006 FIXTURE_VARIANT_ADD(ftruncate, wt_wt) {
4007 /* clang-format on */
4008 .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
4009 .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
4010 .expected_open_result = 0,
4011 .expected_ftruncate_result = 0,
4012 };
4013
4014 /* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate,wt_t)4015 FIXTURE_VARIANT_ADD(ftruncate, wt_t) {
4016 /* clang-format on */
4017 .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
4018 .allowed = LANDLOCK_ACCESS_FS_TRUNCATE,
4019 .expected_open_result = EACCES,
4020 };
4021
TEST_F_FORK(ftruncate,open_and_ftruncate)4022 TEST_F_FORK(ftruncate, open_and_ftruncate)
4023 {
4024 const char *const path = file1_s1d1;
4025 const struct rule rules[] = {
4026 {
4027 .path = path,
4028 .access = variant->allowed,
4029 },
4030 {},
4031 };
4032 int fd, ruleset_fd;
4033
4034 /* Enables Landlock. */
4035 ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
4036 ASSERT_LE(0, ruleset_fd);
4037 enforce_ruleset(_metadata, ruleset_fd);
4038 ASSERT_EQ(0, close(ruleset_fd));
4039
4040 fd = open(path, O_WRONLY);
4041 EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));
4042 if (fd >= 0) {
4043 EXPECT_EQ(variant->expected_ftruncate_result,
4044 test_ftruncate(fd));
4045 ASSERT_EQ(0, close(fd));
4046 }
4047 }
4048
TEST_F_FORK(ftruncate,open_and_ftruncate_in_different_processes)4049 TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes)
4050 {
4051 int child, fd, status;
4052 int socket_fds[2];
4053
4054 ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
4055 socket_fds));
4056
4057 child = fork();
4058 ASSERT_LE(0, child);
4059 if (child == 0) {
4060 /*
4061 * Enables Landlock in the child process, open a file descriptor
4062 * where truncation is forbidden and send it to the
4063 * non-landlocked parent process.
4064 */
4065 const char *const path = file1_s1d1;
4066 const struct rule rules[] = {
4067 {
4068 .path = path,
4069 .access = variant->allowed,
4070 },
4071 {},
4072 };
4073 int fd, ruleset_fd;
4074
4075 ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
4076 ASSERT_LE(0, ruleset_fd);
4077 enforce_ruleset(_metadata, ruleset_fd);
4078 ASSERT_EQ(0, close(ruleset_fd));
4079
4080 fd = open(path, O_WRONLY);
4081 ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));
4082
4083 if (fd >= 0) {
4084 ASSERT_EQ(0, send_fd(socket_fds[0], fd));
4085 ASSERT_EQ(0, close(fd));
4086 }
4087
4088 ASSERT_EQ(0, close(socket_fds[0]));
4089
4090 _exit(_metadata->exit_code);
4091 return;
4092 }
4093
4094 if (variant->expected_open_result == 0) {
4095 fd = recv_fd(socket_fds[1]);
4096 ASSERT_LE(0, fd);
4097
4098 EXPECT_EQ(variant->expected_ftruncate_result,
4099 test_ftruncate(fd));
4100 ASSERT_EQ(0, close(fd));
4101 }
4102
4103 ASSERT_EQ(child, waitpid(child, &status, 0));
4104 ASSERT_EQ(1, WIFEXITED(status));
4105 ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
4106
4107 ASSERT_EQ(0, close(socket_fds[0]));
4108 ASSERT_EQ(0, close(socket_fds[1]));
4109 }
4110
4111 /* Invokes the FS_IOC_GETFLAGS IOCTL and returns its errno or 0. */
test_fs_ioc_getflags_ioctl(int fd)4112 static int test_fs_ioc_getflags_ioctl(int fd)
4113 {
4114 uint32_t flags;
4115
4116 if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0)
4117 return errno;
4118 return 0;
4119 }
4120
TEST(memfd_ftruncate_and_ioctl)4121 TEST(memfd_ftruncate_and_ioctl)
4122 {
4123 const struct landlock_ruleset_attr attr = {
4124 .handled_access_fs = ACCESS_ALL,
4125 };
4126 int ruleset_fd, fd, i;
4127
4128 /*
4129 * We exercise the same test both with and without Landlock enabled, to
4130 * ensure that it behaves the same in both cases.
4131 */
4132 for (i = 0; i < 2; i++) {
4133 /* Creates a new memfd. */
4134 fd = memfd_create("name", MFD_CLOEXEC);
4135 ASSERT_LE(0, fd);
4136
4137 /*
4138 * Checks that operations associated with the opened file
4139 * (ftruncate, ioctl) are permitted on file descriptors that are
4140 * created in ways other than open(2).
4141 */
4142 EXPECT_EQ(0, test_ftruncate(fd));
4143 EXPECT_EQ(0, test_fs_ioc_getflags_ioctl(fd));
4144
4145 ASSERT_EQ(0, close(fd));
4146
4147 /* Enables Landlock. */
4148 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
4149 ASSERT_LE(0, ruleset_fd);
4150 enforce_ruleset(_metadata, ruleset_fd);
4151 ASSERT_EQ(0, close(ruleset_fd));
4152 }
4153 }
4154
test_fionread_ioctl(int fd)4155 static int test_fionread_ioctl(int fd)
4156 {
4157 size_t sz = 0;
4158
4159 if (ioctl(fd, FIONREAD, &sz) < 0 && errno == EACCES)
4160 return errno;
4161 return 0;
4162 }
4163
TEST_F_FORK(layout1,o_path_ftruncate_and_ioctl)4164 TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
4165 {
4166 const struct landlock_ruleset_attr attr = {
4167 .handled_access_fs = ACCESS_ALL,
4168 };
4169 int ruleset_fd, fd;
4170
4171 /*
4172 * Checks that for files opened with O_PATH, both ioctl(2) and
4173 * ftruncate(2) yield EBADF, as it is documented in open(2) for the
4174 * O_PATH flag.
4175 */
4176 fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
4177 ASSERT_LE(0, fd);
4178
4179 EXPECT_EQ(EBADF, test_ftruncate(fd));
4180 EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));
4181
4182 ASSERT_EQ(0, close(fd));
4183
4184 /* Enables Landlock. */
4185 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
4186 ASSERT_LE(0, ruleset_fd);
4187 enforce_ruleset(_metadata, ruleset_fd);
4188 ASSERT_EQ(0, close(ruleset_fd));
4189
4190 /*
4191 * Checks that after enabling Landlock,
4192 * - the file can still be opened with O_PATH
4193 * - both ioctl and truncate still yield EBADF (not EACCES).
4194 */
4195 fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
4196 ASSERT_LE(0, fd);
4197
4198 EXPECT_EQ(EBADF, test_ftruncate(fd));
4199 EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));
4200
4201 ASSERT_EQ(0, close(fd));
4202 }
4203
4204 /*
4205 * ioctl_error - generically call the given ioctl with a pointer to a
4206 * sufficiently large zeroed-out memory region.
4207 *
4208 * Returns the IOCTLs error, or 0.
4209 */
ioctl_error(struct __test_metadata * const _metadata,int fd,unsigned int cmd)4210 static int ioctl_error(struct __test_metadata *const _metadata, int fd,
4211 unsigned int cmd)
4212 {
4213 char buf[128]; /* sufficiently large */
4214 int res, stdinbak_fd;
4215
4216 /*
4217 * Depending on the IOCTL command, parts of the zeroed-out buffer might
4218 * be interpreted as file descriptor numbers. We do not want to
4219 * accidentally operate on file descriptor 0 (stdin), so we temporarily
4220 * move stdin to a different FD and close FD 0 for the IOCTL call.
4221 */
4222 stdinbak_fd = dup(0);
4223 ASSERT_LT(0, stdinbak_fd);
4224 ASSERT_EQ(0, close(0));
4225
4226 /* Invokes the IOCTL with a zeroed-out buffer. */
4227 bzero(&buf, sizeof(buf));
4228 res = ioctl(fd, cmd, &buf);
4229
4230 /* Restores the old FD 0 and closes the backup FD. */
4231 ASSERT_EQ(0, dup2(stdinbak_fd, 0));
4232 ASSERT_EQ(0, close(stdinbak_fd));
4233
4234 if (res < 0)
4235 return errno;
4236
4237 return 0;
4238 }
4239
4240 /* Define some linux/falloc.h IOCTL commands which are not available in uapi headers. */
4241 struct space_resv {
4242 __s16 l_type;
4243 __s16 l_whence;
4244 __s64 l_start;
4245 __s64 l_len; /* len == 0 means until end of file */
4246 __s32 l_sysid;
4247 __u32 l_pid;
4248 __s32 l_pad[4]; /* reserved area */
4249 };
4250
4251 #define FS_IOC_RESVSP _IOW('X', 40, struct space_resv)
4252 #define FS_IOC_UNRESVSP _IOW('X', 41, struct space_resv)
4253 #define FS_IOC_RESVSP64 _IOW('X', 42, struct space_resv)
4254 #define FS_IOC_UNRESVSP64 _IOW('X', 43, struct space_resv)
4255 #define FS_IOC_ZERO_RANGE _IOW('X', 57, struct space_resv)
4256
4257 /*
4258 * Tests a series of blanket-permitted and denied IOCTLs.
4259 */
TEST_F_FORK(layout1,blanket_permitted_ioctls)4260 TEST_F_FORK(layout1, blanket_permitted_ioctls)
4261 {
4262 const struct landlock_ruleset_attr attr = {
4263 .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4264 };
4265 int ruleset_fd, fd;
4266
4267 /* Enables Landlock. */
4268 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
4269 ASSERT_LE(0, ruleset_fd);
4270 enforce_ruleset(_metadata, ruleset_fd);
4271 ASSERT_EQ(0, close(ruleset_fd));
4272
4273 fd = open("/dev/null", O_RDWR | O_CLOEXEC);
4274 ASSERT_LE(0, fd);
4275
4276 /*
4277 * Checks permitted commands.
4278 * These ones may return errors, but should not be blocked by Landlock.
4279 */
4280 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOCLEX));
4281 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONCLEX));
4282 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONBIO));
4283 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOASYNC));
4284 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOQSIZE));
4285 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIFREEZE));
4286 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FITHAW));
4287 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_FIEMAP));
4288 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIGETBSZ));
4289 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONE));
4290 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONERANGE));
4291 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIDEDUPERANGE));
4292 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSUUID));
4293 EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSSYSFSPATH));
4294
4295 /*
4296 * Checks blocked commands.
4297 * A call to a blocked IOCTL command always returns EACCES.
4298 */
4299 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
4300 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFLAGS));
4301 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_SETFLAGS));
4302 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSGETXATTR));
4303 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSSETXATTR));
4304 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIBMAP));
4305 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP));
4306 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP64));
4307 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP));
4308 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP64));
4309 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE));
4310
4311 /* Default case is also blocked. */
4312 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee));
4313
4314 ASSERT_EQ(0, close(fd));
4315 }
4316
4317 /*
4318 * Named pipes are not governed by the LANDLOCK_ACCESS_FS_IOCTL_DEV right,
4319 * because they are not character or block devices.
4320 */
TEST_F_FORK(layout1,named_pipe_ioctl)4321 TEST_F_FORK(layout1, named_pipe_ioctl)
4322 {
4323 pid_t child_pid;
4324 int fd, ruleset_fd;
4325 const char *const path = file1_s1d1;
4326 const struct landlock_ruleset_attr attr = {
4327 .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4328 };
4329
4330 ASSERT_EQ(0, unlink(path));
4331 ASSERT_EQ(0, mkfifo(path, 0600));
4332
4333 /* Enables Landlock. */
4334 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
4335 ASSERT_LE(0, ruleset_fd);
4336 enforce_ruleset(_metadata, ruleset_fd);
4337 ASSERT_EQ(0, close(ruleset_fd));
4338
4339 /* The child process opens the pipe for writing. */
4340 child_pid = fork();
4341 ASSERT_NE(-1, child_pid);
4342 if (child_pid == 0) {
4343 fd = open(path, O_WRONLY);
4344 close(fd);
4345 exit(0);
4346 }
4347
4348 fd = open(path, O_RDONLY);
4349 ASSERT_LE(0, fd);
4350
4351 /* FIONREAD is implemented by pipefifo_fops. */
4352 EXPECT_EQ(0, test_fionread_ioctl(fd));
4353
4354 ASSERT_EQ(0, close(fd));
4355 ASSERT_EQ(0, unlink(path));
4356
4357 ASSERT_EQ(child_pid, waitpid(child_pid, NULL, 0));
4358 }
4359
4360 /* For named UNIX domain sockets, no IOCTL restrictions apply. */
TEST_F_FORK(layout1,named_unix_domain_socket_ioctl)4361 TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
4362 {
4363 const char *const path = file1_s1d1;
4364 int srv_fd, cli_fd, ruleset_fd;
4365 socklen_t size;
4366 struct sockaddr_un srv_un, cli_un;
4367 const struct landlock_ruleset_attr attr = {
4368 .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4369 };
4370
4371 /* Sets up a server */
4372 srv_un.sun_family = AF_UNIX;
4373 strncpy(srv_un.sun_path, path, sizeof(srv_un.sun_path));
4374
4375 ASSERT_EQ(0, unlink(path));
4376 srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
4377 ASSERT_LE(0, srv_fd);
4378
4379 size = offsetof(struct sockaddr_un, sun_path) + strlen(srv_un.sun_path);
4380 ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, size));
4381 ASSERT_EQ(0, listen(srv_fd, 10 /* qlen */));
4382
4383 /* Enables Landlock. */
4384 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
4385 ASSERT_LE(0, ruleset_fd);
4386 enforce_ruleset(_metadata, ruleset_fd);
4387 ASSERT_EQ(0, close(ruleset_fd));
4388
4389 /* Sets up a client connection to it */
4390 cli_un.sun_family = AF_UNIX;
4391 cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
4392 ASSERT_LE(0, cli_fd);
4393
4394 size = offsetof(struct sockaddr_un, sun_path) + strlen(cli_un.sun_path);
4395 ASSERT_EQ(0, bind(cli_fd, (struct sockaddr *)&cli_un, size));
4396
4397 bzero(&cli_un, sizeof(cli_un));
4398 cli_un.sun_family = AF_UNIX;
4399 strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path));
4400 size = offsetof(struct sockaddr_un, sun_path) + strlen(cli_un.sun_path);
4401
4402 ASSERT_EQ(0, connect(cli_fd, (struct sockaddr *)&cli_un, size));
4403
4404 /* FIONREAD and other IOCTLs should not be forbidden. */
4405 EXPECT_EQ(0, test_fionread_ioctl(cli_fd));
4406
4407 ASSERT_EQ(0, close(cli_fd));
4408 }
4409
4410 /* clang-format off */
FIXTURE(ioctl)4411 FIXTURE(ioctl) {};
4412
FIXTURE_SETUP(ioctl)4413 FIXTURE_SETUP(ioctl) {};
4414
FIXTURE_TEARDOWN(ioctl)4415 FIXTURE_TEARDOWN(ioctl) {};
4416 /* clang-format on */
4417
FIXTURE_VARIANT(ioctl)4418 FIXTURE_VARIANT(ioctl)
4419 {
4420 const __u64 handled;
4421 const __u64 allowed;
4422 const mode_t open_mode;
4423 /*
4424 * FIONREAD is used as a characteristic device-specific IOCTL command.
4425 * It is implemented in fs/ioctl.c for regular files,
4426 * but we do not blanket-permit it for devices.
4427 */
4428 const int expected_fionread_result;
4429 };
4430
4431 /* clang-format off */
FIXTURE_VARIANT_ADD(ioctl,handled_i_allowed_none)4432 FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_none) {
4433 /* clang-format on */
4434 .handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4435 .allowed = 0,
4436 .open_mode = O_RDWR,
4437 .expected_fionread_result = EACCES,
4438 };
4439
4440 /* clang-format off */
FIXTURE_VARIANT_ADD(ioctl,handled_i_allowed_i)4441 FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_i) {
4442 /* clang-format on */
4443 .handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4444 .allowed = LANDLOCK_ACCESS_FS_IOCTL_DEV,
4445 .open_mode = O_RDWR,
4446 .expected_fionread_result = 0,
4447 };
4448
4449 /* clang-format off */
FIXTURE_VARIANT_ADD(ioctl,unhandled)4450 FIXTURE_VARIANT_ADD(ioctl, unhandled) {
4451 /* clang-format on */
4452 .handled = LANDLOCK_ACCESS_FS_EXECUTE,
4453 .allowed = LANDLOCK_ACCESS_FS_EXECUTE,
4454 .open_mode = O_RDWR,
4455 .expected_fionread_result = 0,
4456 };
4457
TEST_F_FORK(ioctl,handle_dir_access_file)4458 TEST_F_FORK(ioctl, handle_dir_access_file)
4459 {
4460 const int flag = 0;
4461 const struct rule rules[] = {
4462 {
4463 .path = "/dev",
4464 .access = variant->allowed,
4465 },
4466 {},
4467 };
4468 int file_fd, ruleset_fd;
4469
4470 /* Enables Landlock. */
4471 ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
4472 ASSERT_LE(0, ruleset_fd);
4473 enforce_ruleset(_metadata, ruleset_fd);
4474 ASSERT_EQ(0, close(ruleset_fd));
4475
4476 file_fd = open("/dev/zero", variant->open_mode);
4477 ASSERT_LE(0, file_fd);
4478
4479 /* Checks that IOCTL commands return the expected errors. */
4480 EXPECT_EQ(variant->expected_fionread_result,
4481 test_fionread_ioctl(file_fd));
4482
4483 /* Checks that unrestrictable commands are unrestricted. */
4484 EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
4485 EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
4486 EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
4487 EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
4488 EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
4489
4490 ASSERT_EQ(0, close(file_fd));
4491 }
4492
TEST_F_FORK(ioctl,handle_dir_access_dir)4493 TEST_F_FORK(ioctl, handle_dir_access_dir)
4494 {
4495 const int flag = 0;
4496 const struct rule rules[] = {
4497 {
4498 .path = "/dev",
4499 .access = variant->allowed,
4500 },
4501 {},
4502 };
4503 int dir_fd, ruleset_fd;
4504
4505 /* Enables Landlock. */
4506 ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
4507 ASSERT_LE(0, ruleset_fd);
4508 enforce_ruleset(_metadata, ruleset_fd);
4509 ASSERT_EQ(0, close(ruleset_fd));
4510
4511 /*
4512 * Ignore variant->open_mode for this test, as we intend to open a
4513 * directory. If the directory can not be opened, the variant is
4514 * infeasible to test with an opened directory.
4515 */
4516 dir_fd = open("/dev", O_RDONLY);
4517 if (dir_fd < 0)
4518 return;
4519
4520 /*
4521 * Checks that IOCTL commands return the expected errors.
4522 * We do not use the expected values from the fixture here.
4523 *
4524 * When using IOCTL on a directory, no Landlock restrictions apply.
4525 */
4526 EXPECT_EQ(0, test_fionread_ioctl(dir_fd));
4527
4528 /* Checks that unrestrictable commands are unrestricted. */
4529 EXPECT_EQ(0, ioctl(dir_fd, FIOCLEX));
4530 EXPECT_EQ(0, ioctl(dir_fd, FIONCLEX));
4531 EXPECT_EQ(0, ioctl(dir_fd, FIONBIO, &flag));
4532 EXPECT_EQ(0, ioctl(dir_fd, FIOASYNC, &flag));
4533 EXPECT_EQ(0, ioctl(dir_fd, FIGETBSZ, &flag));
4534
4535 ASSERT_EQ(0, close(dir_fd));
4536 }
4537
TEST_F_FORK(ioctl,handle_file_access_file)4538 TEST_F_FORK(ioctl, handle_file_access_file)
4539 {
4540 const int flag = 0;
4541 const struct rule rules[] = {
4542 {
4543 .path = "/dev/zero",
4544 .access = variant->allowed,
4545 },
4546 {},
4547 };
4548 int file_fd, ruleset_fd;
4549
4550 /* Enables Landlock. */
4551 ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
4552 ASSERT_LE(0, ruleset_fd);
4553 enforce_ruleset(_metadata, ruleset_fd);
4554 ASSERT_EQ(0, close(ruleset_fd));
4555
4556 file_fd = open("/dev/zero", variant->open_mode);
4557 ASSERT_LE(0, file_fd)
4558 {
4559 TH_LOG("Failed to open /dev/zero: %s", strerror(errno));
4560 }
4561
4562 /* Checks that IOCTL commands return the expected errors. */
4563 EXPECT_EQ(variant->expected_fionread_result,
4564 test_fionread_ioctl(file_fd));
4565
4566 /* Checks that unrestrictable commands are unrestricted. */
4567 EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
4568 EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
4569 EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
4570 EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
4571 EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
4572
4573 ASSERT_EQ(0, close(file_fd));
4574 }
4575
4576 /* clang-format off */
FIXTURE(layout1_bind)4577 FIXTURE(layout1_bind) {};
4578 /* clang-format on */
4579
4580 static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3";
4581 static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1";
4582
4583 /* Move targets for disconnected path tests. */
4584 static const char dir_s4d1[] = TMP_DIR "/s4d1";
4585 static const char file1_s4d1[] = TMP_DIR "/s4d1/f1";
4586 static const char file2_s4d1[] = TMP_DIR "/s4d1/f2";
4587 static const char dir_s4d2[] = TMP_DIR "/s4d1/s4d2";
4588 static const char file1_s4d2[] = TMP_DIR "/s4d1/s4d2/f1";
4589 static const char file1_name[] = "f1";
4590 static const char file2_name[] = "f2";
4591
FIXTURE_SETUP(layout1_bind)4592 FIXTURE_SETUP(layout1_bind)
4593 {
4594 prepare_layout(_metadata);
4595
4596 create_layout1(_metadata);
4597
4598 set_cap(_metadata, CAP_SYS_ADMIN);
4599 ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL));
4600 clear_cap(_metadata, CAP_SYS_ADMIN);
4601 }
4602
FIXTURE_TEARDOWN_PARENT(layout1_bind)4603 FIXTURE_TEARDOWN_PARENT(layout1_bind)
4604 {
4605 /* umount(dir_s2d2)) is handled by namespace lifetime. */
4606
4607 remove_path(file1_s4d1);
4608 remove_path(file2_s4d1);
4609
4610 remove_layout1(_metadata);
4611
4612 cleanup_layout(_metadata);
4613 }
4614
4615 /*
4616 * layout1_bind hierarchy:
4617 *
4618 * tmp
4619 * ├── s1d1
4620 * │ ├── f1
4621 * │ ├── f2
4622 * │ └── s1d2
4623 * │ ├── f1
4624 * │ ├── f2
4625 * │ └── s1d3 [disconnected by path_disconnected]
4626 * │ ├── f1
4627 * │ └── f2
4628 * ├── s2d1
4629 * │ ├── f1
4630 * │ └── s2d2 [bind mount from s1d2]
4631 * │ ├── f1
4632 * │ ├── f2
4633 * │ └── s1d3
4634 * │ ├── f1
4635 * │ └── f2
4636 * ├── s3d1
4637 * │ └── s3d2
4638 * │ └── s3d3
4639 * └── s4d1 [renamed from s1d3 by path_disconnected]
4640 * ├── f1
4641 * ├── f2
4642 * └── s4d2
4643 * └── f1
4644 */
4645
TEST_F_FORK(layout1_bind,no_restriction)4646 TEST_F_FORK(layout1_bind, no_restriction)
4647 {
4648 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
4649 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
4650 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
4651 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
4652 ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY));
4653 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
4654
4655 ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY));
4656 ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY));
4657 ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY));
4658 ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY));
4659 ASSERT_EQ(ENOENT, test_open(dir_s2d3, O_RDONLY));
4660 ASSERT_EQ(ENOENT, test_open(file1_s2d3, O_RDONLY));
4661
4662 ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY));
4663 ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY));
4664
4665 ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY));
4666 }
4667
TEST_F_FORK(layout1_bind,same_content_same_file)4668 TEST_F_FORK(layout1_bind, same_content_same_file)
4669 {
4670 /*
4671 * Sets access right on parent directories of both source and
4672 * destination mount points.
4673 */
4674 const struct rule layer1_parent[] = {
4675 {
4676 .path = dir_s1d1,
4677 .access = ACCESS_RO,
4678 },
4679 {
4680 .path = dir_s2d1,
4681 .access = ACCESS_RW,
4682 },
4683 {},
4684 };
4685 /*
4686 * Sets access rights on the same bind-mounted directories. The result
4687 * should be ACCESS_RW for both directories, but not both hierarchies
4688 * because of the first layer.
4689 */
4690 const struct rule layer2_mount_point[] = {
4691 {
4692 .path = dir_s1d2,
4693 .access = LANDLOCK_ACCESS_FS_READ_FILE,
4694 },
4695 {
4696 .path = dir_s2d2,
4697 .access = ACCESS_RW,
4698 },
4699 {},
4700 };
4701 /* Only allow read-access to the s1d3 hierarchies. */
4702 const struct rule layer3_source[] = {
4703 {
4704 .path = dir_s1d3,
4705 .access = LANDLOCK_ACCESS_FS_READ_FILE,
4706 },
4707 {},
4708 };
4709 /* Removes all access rights. */
4710 const struct rule layer4_destination[] = {
4711 {
4712 .path = bind_file1_s1d3,
4713 .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
4714 },
4715 {},
4716 };
4717 int ruleset_fd;
4718
4719 /* Sets rules for the parent directories. */
4720 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent);
4721 ASSERT_LE(0, ruleset_fd);
4722 enforce_ruleset(_metadata, ruleset_fd);
4723 ASSERT_EQ(0, close(ruleset_fd));
4724
4725 /* Checks source hierarchy. */
4726 ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
4727 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
4728 ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
4729
4730 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
4731 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
4732 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
4733
4734 /* Checks destination hierarchy. */
4735 ASSERT_EQ(0, test_open(file1_s2d1, O_RDWR));
4736 ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY));
4737
4738 ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR));
4739 ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY));
4740
4741 /* Sets rules for the mount points. */
4742 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point);
4743 ASSERT_LE(0, ruleset_fd);
4744 enforce_ruleset(_metadata, ruleset_fd);
4745 ASSERT_EQ(0, close(ruleset_fd));
4746
4747 /* Checks source hierarchy. */
4748 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
4749 ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
4750 ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
4751
4752 ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
4753 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
4754 ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
4755
4756 /* Checks destination hierarchy. */
4757 ASSERT_EQ(EACCES, test_open(file1_s2d1, O_RDONLY));
4758 ASSERT_EQ(EACCES, test_open(file1_s2d1, O_WRONLY));
4759 ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY));
4760
4761 ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR));
4762 ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY));
4763 ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY));
4764
4765 /* Sets a (shared) rule only on the source. */
4766 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source);
4767 ASSERT_LE(0, ruleset_fd);
4768 enforce_ruleset(_metadata, ruleset_fd);
4769 ASSERT_EQ(0, close(ruleset_fd));
4770
4771 /* Checks source hierarchy. */
4772 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY));
4773 ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
4774 ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
4775
4776 ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
4777 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
4778 ASSERT_EQ(EACCES, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
4779
4780 /* Checks destination hierarchy. */
4781 ASSERT_EQ(EACCES, test_open(file1_s2d2, O_RDONLY));
4782 ASSERT_EQ(EACCES, test_open(file1_s2d2, O_WRONLY));
4783 ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY));
4784
4785 ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY));
4786 ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY));
4787 ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY));
4788
4789 /* Sets a (shared) rule only on the destination. */
4790 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination);
4791 ASSERT_LE(0, ruleset_fd);
4792 enforce_ruleset(_metadata, ruleset_fd);
4793 ASSERT_EQ(0, close(ruleset_fd));
4794
4795 /* Checks source hierarchy. */
4796 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
4797 ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
4798
4799 /* Checks destination hierarchy. */
4800 ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_RDONLY));
4801 ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY));
4802 }
4803
TEST_F_FORK(layout1_bind,reparent_cross_mount)4804 TEST_F_FORK(layout1_bind, reparent_cross_mount)
4805 {
4806 const struct rule layer1[] = {
4807 {
4808 /* dir_s2d1 is beneath the dir_s2d2 mount point. */
4809 .path = dir_s2d1,
4810 .access = LANDLOCK_ACCESS_FS_REFER,
4811 },
4812 {
4813 .path = bind_dir_s1d3,
4814 .access = LANDLOCK_ACCESS_FS_EXECUTE,
4815 },
4816 {},
4817 };
4818 int ruleset_fd = create_ruleset(
4819 _metadata,
4820 LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1);
4821
4822 ASSERT_LE(0, ruleset_fd);
4823 enforce_ruleset(_metadata, ruleset_fd);
4824 ASSERT_EQ(0, close(ruleset_fd));
4825
4826 /* Checks basic denied move. */
4827 ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2));
4828 ASSERT_EQ(EXDEV, errno);
4829
4830 /* Checks real cross-mount move (Landlock is not involved). */
4831 ASSERT_EQ(-1, rename(file1_s2d1, file1_s2d2));
4832 ASSERT_EQ(EXDEV, errno);
4833
4834 /* Checks move that will give more accesses. */
4835 ASSERT_EQ(-1, rename(file1_s2d2, bind_file1_s1d3));
4836 ASSERT_EQ(EXDEV, errno);
4837
4838 /* Checks legitimate downgrade move. */
4839 ASSERT_EQ(0, rename(bind_file1_s1d3, file1_s2d2));
4840 }
4841
4842 /*
4843 * Make sure access to file through a disconnected path works as expected.
4844 * This test moves s1d3 to s4d1.
4845 */
TEST_F_FORK(layout1_bind,path_disconnected)4846 TEST_F_FORK(layout1_bind, path_disconnected)
4847 {
4848 const struct rule layer1_allow_all[] = {
4849 {
4850 .path = TMP_DIR,
4851 .access = ACCESS_ALL,
4852 },
4853 {},
4854 };
4855 const struct rule layer2_allow_just_f1[] = {
4856 {
4857 .path = file1_s1d3,
4858 .access = LANDLOCK_ACCESS_FS_READ_FILE,
4859 },
4860 {},
4861 };
4862 const struct rule layer3_only_s1d2[] = {
4863 {
4864 .path = dir_s1d2,
4865 .access = LANDLOCK_ACCESS_FS_READ_FILE,
4866 },
4867 {},
4868 };
4869
4870 /* Landlock should not deny access just because it is disconnected. */
4871 int ruleset_fd_l1 =
4872 create_ruleset(_metadata, ACCESS_ALL, layer1_allow_all);
4873
4874 /* Creates the new ruleset now before we move the dir containing the file. */
4875 int ruleset_fd_l2 =
4876 create_ruleset(_metadata, ACCESS_RW, layer2_allow_just_f1);
4877 int ruleset_fd_l3 =
4878 create_ruleset(_metadata, ACCESS_RW, layer3_only_s1d2);
4879 int bind_s1d3_fd;
4880
4881 ASSERT_LE(0, ruleset_fd_l1);
4882 ASSERT_LE(0, ruleset_fd_l2);
4883 ASSERT_LE(0, ruleset_fd_l3);
4884
4885 enforce_ruleset(_metadata, ruleset_fd_l1);
4886 EXPECT_EQ(0, close(ruleset_fd_l1));
4887
4888 bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
4889 ASSERT_LE(0, bind_s1d3_fd);
4890
4891 /* Tests access is possible before we move. */
4892 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
4893 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
4894 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, "..", O_RDONLY | O_DIRECTORY));
4895
4896 /* Makes it disconnected. */
4897 ASSERT_EQ(0, rename(dir_s1d3, dir_s4d1))
4898 {
4899 TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d1,
4900 strerror(errno));
4901 }
4902
4903 /* Tests that access is still possible. */
4904 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
4905 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
4906
4907 /*
4908 * Tests that ".." is not possible (not because of Landlock, but just
4909 * because it's disconnected).
4910 */
4911 EXPECT_EQ(ENOENT,
4912 test_open_rel(bind_s1d3_fd, "..", O_RDONLY | O_DIRECTORY));
4913
4914 /* This should still work with a narrower rule. */
4915 enforce_ruleset(_metadata, ruleset_fd_l2);
4916 EXPECT_EQ(0, close(ruleset_fd_l2));
4917
4918 EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY));
4919 /*
4920 * Accessing a file through a disconnected file descriptor can still be
4921 * allowed by a rule tied to this file, even if it is no longer visible in
4922 * its mount point.
4923 */
4924 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
4925 EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
4926
4927 enforce_ruleset(_metadata, ruleset_fd_l3);
4928 EXPECT_EQ(0, close(ruleset_fd_l3));
4929
4930 EXPECT_EQ(EACCES, test_open(file1_s4d1, O_RDONLY));
4931 /*
4932 * Accessing a file through a disconnected file descriptor can still be
4933 * allowed by a rule tied to the original mount point, even if it is no
4934 * longer visible in its mount point.
4935 */
4936 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
4937 EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
4938 }
4939
4940 /*
4941 * Test that renameat with disconnected paths works under Landlock. This test
4942 * moves s1d3 to s4d2, so that we can have a rule allowing refers on the move
4943 * target's immediate parent.
4944 */
TEST_F_FORK(layout1_bind,path_disconnected_rename)4945 TEST_F_FORK(layout1_bind, path_disconnected_rename)
4946 {
4947 const struct rule layer1[] = {
4948 {
4949 .path = dir_s1d2,
4950 .access = LANDLOCK_ACCESS_FS_REFER |
4951 LANDLOCK_ACCESS_FS_MAKE_DIR |
4952 LANDLOCK_ACCESS_FS_REMOVE_DIR |
4953 LANDLOCK_ACCESS_FS_MAKE_REG |
4954 LANDLOCK_ACCESS_FS_REMOVE_FILE |
4955 LANDLOCK_ACCESS_FS_READ_FILE,
4956 },
4957 {
4958 .path = dir_s4d1,
4959 .access = LANDLOCK_ACCESS_FS_REFER |
4960 LANDLOCK_ACCESS_FS_MAKE_DIR |
4961 LANDLOCK_ACCESS_FS_REMOVE_DIR |
4962 LANDLOCK_ACCESS_FS_MAKE_REG |
4963 LANDLOCK_ACCESS_FS_REMOVE_FILE |
4964 LANDLOCK_ACCESS_FS_READ_FILE,
4965 },
4966 {}
4967 };
4968
4969 /* This layer only handles LANDLOCK_ACCESS_FS_READ_FILE. */
4970 const struct rule layer2_only_s1d2[] = {
4971 {
4972 .path = dir_s1d2,
4973 .access = LANDLOCK_ACCESS_FS_READ_FILE,
4974 },
4975 {},
4976 };
4977 int ruleset_fd_l1, ruleset_fd_l2;
4978 pid_t child_pid;
4979 int bind_s1d3_fd, status;
4980
4981 ASSERT_EQ(0, mkdir(dir_s4d1, 0755))
4982 {
4983 TH_LOG("Failed to create %s: %s", dir_s4d1, strerror(errno));
4984 }
4985 ruleset_fd_l1 = create_ruleset(_metadata, ACCESS_ALL, layer1);
4986 ruleset_fd_l2 = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
4987 layer2_only_s1d2);
4988 ASSERT_LE(0, ruleset_fd_l1);
4989 ASSERT_LE(0, ruleset_fd_l2);
4990
4991 enforce_ruleset(_metadata, ruleset_fd_l1);
4992 EXPECT_EQ(0, close(ruleset_fd_l1));
4993
4994 bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
4995 ASSERT_LE(0, bind_s1d3_fd);
4996 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
4997
4998 /* Tests ENOENT priority over EACCES for disconnected directory. */
4999 EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, "..", O_DIRECTORY));
5000 ASSERT_EQ(0, rename(dir_s1d3, dir_s4d2))
5001 {
5002 TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d2,
5003 strerror(errno));
5004 }
5005 EXPECT_EQ(ENOENT, test_open_rel(bind_s1d3_fd, "..", O_DIRECTORY));
5006
5007 /*
5008 * The file is no longer under s1d2 but we should still be able to access it
5009 * with layer 2 because its mount point is evaluated as the first valid
5010 * directory because it was initially a parent. Do a fork to test this so
5011 * we don't prevent ourselves from renaming it back later.
5012 */
5013 child_pid = fork();
5014 ASSERT_LE(0, child_pid);
5015 if (child_pid == 0) {
5016 enforce_ruleset(_metadata, ruleset_fd_l2);
5017 EXPECT_EQ(0, close(ruleset_fd_l2));
5018 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
5019 EXPECT_EQ(EACCES, test_open(file1_s4d2, O_RDONLY));
5020
5021 /*
5022 * Tests that access widening checks indeed prevents us from renaming it
5023 * back.
5024 */
5025 EXPECT_EQ(-1, rename(dir_s4d2, dir_s1d3));
5026 EXPECT_EQ(EXDEV, errno);
5027
5028 /*
5029 * Including through the now disconnected fd (but it should return
5030 * EXDEV).
5031 */
5032 EXPECT_EQ(-1, renameat(bind_s1d3_fd, file1_name, AT_FDCWD,
5033 file1_s2d2));
5034 EXPECT_EQ(EXDEV, errno);
5035 _exit(_metadata->exit_code);
5036 return;
5037 }
5038
5039 EXPECT_EQ(child_pid, waitpid(child_pid, &status, 0));
5040 EXPECT_EQ(1, WIFEXITED(status));
5041 EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
5042
5043 ASSERT_EQ(0, rename(dir_s4d2, dir_s1d3))
5044 {
5045 TH_LOG("Failed to rename %s back to %s: %s", dir_s4d1, dir_s1d3,
5046 strerror(errno));
5047 }
5048
5049 /* Now checks that we can access it under l2. */
5050 child_pid = fork();
5051 ASSERT_LE(0, child_pid);
5052 if (child_pid == 0) {
5053 enforce_ruleset(_metadata, ruleset_fd_l2);
5054 EXPECT_EQ(0, close(ruleset_fd_l2));
5055 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
5056 EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
5057 _exit(_metadata->exit_code);
5058 return;
5059 }
5060
5061 EXPECT_EQ(child_pid, waitpid(child_pid, &status, 0));
5062 EXPECT_EQ(1, WIFEXITED(status));
5063 EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
5064
5065 /*
5066 * Also test that we can rename via a disconnected path. We move the
5067 * dir back to the disconnected place first, then we rename file1 to
5068 * file2 through our dir fd.
5069 */
5070 ASSERT_EQ(0, rename(dir_s1d3, dir_s4d2))
5071 {
5072 TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d2,
5073 strerror(errno));
5074 }
5075 ASSERT_EQ(0,
5076 renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name))
5077 {
5078 TH_LOG("Failed to rename %s to %s within disconnected %s: %s",
5079 file1_name, file2_name, bind_dir_s1d3, strerror(errno));
5080 }
5081 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
5082 ASSERT_EQ(0, renameat(bind_s1d3_fd, file2_name, AT_FDCWD, file1_s2d2))
5083 {
5084 TH_LOG("Failed to rename %s to %s through disconnected %s: %s",
5085 file2_name, file1_s2d2, bind_dir_s1d3, strerror(errno));
5086 }
5087 EXPECT_EQ(0, test_open(file1_s2d2, O_RDONLY));
5088 EXPECT_EQ(0, test_open(file1_s1d2, O_RDONLY));
5089
5090 /* Move it back using the disconnected path as the target. */
5091 ASSERT_EQ(0, renameat(AT_FDCWD, file1_s2d2, bind_s1d3_fd, file1_name))
5092 {
5093 TH_LOG("Failed to rename %s to %s through disconnected %s: %s",
5094 file1_s1d2, file1_name, bind_dir_s1d3, strerror(errno));
5095 }
5096
5097 /* Now make it connected again. */
5098 ASSERT_EQ(0, rename(dir_s4d2, dir_s1d3))
5099 {
5100 TH_LOG("Failed to rename %s back to %s: %s", dir_s4d2, dir_s1d3,
5101 strerror(errno));
5102 }
5103
5104 /* Checks again that we can access it under l2. */
5105 enforce_ruleset(_metadata, ruleset_fd_l2);
5106 EXPECT_EQ(0, close(ruleset_fd_l2));
5107 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
5108 EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
5109 }
5110
5111 /*
5112 * Test that linkat(2) with disconnected paths works under Landlock. This
5113 * test moves s1d3 to s4d1.
5114 */
TEST_F_FORK(layout1_bind,path_disconnected_link)5115 TEST_F_FORK(layout1_bind, path_disconnected_link)
5116 {
5117 /* Ruleset to be applied after renaming s1d3 to s4d1. */
5118 const struct rule layer1[] = {
5119 {
5120 .path = dir_s4d1,
5121 .access = LANDLOCK_ACCESS_FS_REFER |
5122 LANDLOCK_ACCESS_FS_READ_FILE |
5123 LANDLOCK_ACCESS_FS_MAKE_REG |
5124 LANDLOCK_ACCESS_FS_REMOVE_FILE,
5125 },
5126 {
5127 .path = dir_s2d2,
5128 .access = LANDLOCK_ACCESS_FS_REFER |
5129 LANDLOCK_ACCESS_FS_READ_FILE |
5130 LANDLOCK_ACCESS_FS_MAKE_REG |
5131 LANDLOCK_ACCESS_FS_REMOVE_FILE,
5132 },
5133 {}
5134 };
5135 int ruleset_fd, bind_s1d3_fd;
5136
5137 /* Removes unneeded files created by layout1, otherwise it will EEXIST. */
5138 ASSERT_EQ(0, unlink(file1_s1d2));
5139 ASSERT_EQ(0, unlink(file2_s1d3));
5140
5141 bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
5142 ASSERT_LE(0, bind_s1d3_fd);
5143 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
5144
5145 /* Disconnects bind_s1d3_fd. */
5146 ASSERT_EQ(0, rename(dir_s1d3, dir_s4d1))
5147 {
5148 TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d1,
5149 strerror(errno));
5150 }
5151
5152 /* Need this later to test different parent link. */
5153 ASSERT_EQ(0, mkdir(dir_s4d2, 0755))
5154 {
5155 TH_LOG("Failed to create %s: %s", dir_s4d2, strerror(errno));
5156 }
5157
5158 ruleset_fd = create_ruleset(_metadata, ACCESS_ALL, layer1);
5159 ASSERT_LE(0, ruleset_fd);
5160 enforce_ruleset(_metadata, ruleset_fd);
5161 EXPECT_EQ(0, close(ruleset_fd));
5162
5163 /* From disconnected to connected. */
5164 ASSERT_EQ(0, linkat(bind_s1d3_fd, file1_name, AT_FDCWD, file1_s2d2, 0))
5165 {
5166 TH_LOG("Failed to link %s to %s via disconnected %s: %s",
5167 file1_name, file1_s2d2, bind_dir_s1d3, strerror(errno));
5168 }
5169
5170 /* Tests that we can access via the new link... */
5171 EXPECT_EQ(0, test_open(file1_s2d2, O_RDONLY))
5172 {
5173 TH_LOG("Failed to open newly linked %s: %s", file1_s2d2,
5174 strerror(errno));
5175 }
5176
5177 /* ...as well as the old one. */
5178 EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY))
5179 {
5180 TH_LOG("Failed to open original %s: %s", file1_s4d1,
5181 strerror(errno));
5182 }
5183
5184 /* From connected to disconnected. */
5185 ASSERT_EQ(0, unlink(file1_s4d1));
5186 ASSERT_EQ(0, linkat(AT_FDCWD, file1_s2d2, bind_s1d3_fd, file2_name, 0))
5187 {
5188 TH_LOG("Failed to link %s to %s via disconnected %s: %s",
5189 file1_s2d2, file2_name, bind_dir_s1d3, strerror(errno));
5190 }
5191 EXPECT_EQ(0, test_open(file2_s4d1, O_RDONLY));
5192 ASSERT_EQ(0, unlink(file1_s2d2));
5193
5194 /* From disconnected to disconnected (same parent). */
5195 ASSERT_EQ(0,
5196 linkat(bind_s1d3_fd, file2_name, bind_s1d3_fd, file1_name, 0))
5197 {
5198 TH_LOG("Failed to link %s to %s within disconnected %s: %s",
5199 file2_name, file1_name, bind_dir_s1d3, strerror(errno));
5200 }
5201 EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY))
5202 {
5203 TH_LOG("Failed to open newly linked %s: %s", file1_s4d1,
5204 strerror(errno));
5205 }
5206 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY))
5207 {
5208 TH_LOG("Failed to open %s through newly created link under disconnected path: %s",
5209 file1_name, strerror(errno));
5210 }
5211 ASSERT_EQ(0, unlink(file2_s4d1));
5212
5213 /* From disconnected to disconnected (different parent). */
5214 ASSERT_EQ(0,
5215 linkat(bind_s1d3_fd, file1_name, bind_s1d3_fd, "s4d2/f1", 0))
5216 {
5217 TH_LOG("Failed to link %s to %s within disconnected %s: %s",
5218 file1_name, "s4d2/f1", bind_dir_s1d3, strerror(errno));
5219 }
5220 EXPECT_EQ(0, test_open(file1_s4d2, O_RDONLY))
5221 {
5222 TH_LOG("Failed to open %s after link: %s", file1_s4d2,
5223 strerror(errno));
5224 }
5225 EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, "s4d2/f1", O_RDONLY))
5226 {
5227 TH_LOG("Failed to open %s through disconnected path after link: %s",
5228 "s4d2/f1", strerror(errno));
5229 }
5230 }
5231
5232 /*
5233 * layout4_disconnected_leafs with bind mount and renames:
5234 *
5235 * tmp
5236 * ├── s1d1
5237 * │ └── s1d2 [source of the bind mount]
5238 * │ ├── s1d31
5239 * │ │ └── s1d41 [now renamed beneath s3d1]
5240 * │ │ ├── f1
5241 * │ │ └── f2
5242 * │ └── s1d32
5243 * │ └── s1d42 [now renamed beneath s4d1]
5244 * │ ├── f3
5245 * │ └── f4
5246 * ├── s2d1
5247 * │ └── s2d2 [bind mount of s1d2]
5248 * │ ├── s1d31
5249 * │ │ └── s1d41 [opened FD, now renamed beneath s3d1]
5250 * │ │ ├── f1
5251 * │ │ └── f2
5252 * │ └── s1d32
5253 * │ └── s1d42 [opened FD, now renamed beneath s4d1]
5254 * │ ├── f3
5255 * │ └── f4
5256 * ├── s3d1
5257 * │ └── s1d41 [renamed here]
5258 * │ ├── f1
5259 * │ └── f2
5260 * └── s4d1
5261 * └── s1d42 [renamed here]
5262 * ├── f3
5263 * └── f4
5264 */
5265 /* clang-format off */
FIXTURE(layout4_disconnected_leafs)5266 FIXTURE(layout4_disconnected_leafs) {
5267 int s2d2_fd;
5268 };
5269 /* clang-format on */
5270
FIXTURE_SETUP(layout4_disconnected_leafs)5271 FIXTURE_SETUP(layout4_disconnected_leafs)
5272 {
5273 prepare_layout(_metadata);
5274
5275 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f1");
5276 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f2");
5277 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f3");
5278 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f4");
5279 create_directory(_metadata, TMP_DIR "/s2d1/s2d2");
5280 create_directory(_metadata, TMP_DIR "/s3d1");
5281 create_directory(_metadata, TMP_DIR "/s4d1");
5282
5283 self->s2d2_fd =
5284 open(TMP_DIR "/s2d1/s2d2", O_DIRECTORY | O_PATH | O_CLOEXEC);
5285 ASSERT_LE(0, self->s2d2_fd);
5286
5287 set_cap(_metadata, CAP_SYS_ADMIN);
5288 ASSERT_EQ(0, mount(TMP_DIR "/s1d1/s1d2", TMP_DIR "/s2d1/s2d2", NULL,
5289 MS_BIND, NULL));
5290 clear_cap(_metadata, CAP_SYS_ADMIN);
5291 }
5292
FIXTURE_TEARDOWN_PARENT(layout4_disconnected_leafs)5293 FIXTURE_TEARDOWN_PARENT(layout4_disconnected_leafs)
5294 {
5295 /* umount(TMP_DIR "/s2d1") is handled by namespace lifetime. */
5296
5297 /* Removes files after renames. */
5298 remove_path(TMP_DIR "/s3d1/s1d41/f1");
5299 remove_path(TMP_DIR "/s3d1/s1d41/f2");
5300 remove_path(TMP_DIR "/s4d1/s1d42/f1");
5301 remove_path(TMP_DIR "/s4d1/s1d42/f3");
5302 remove_path(TMP_DIR "/s4d1/s1d42/f4");
5303 remove_path(TMP_DIR "/s4d1/s1d42/f5");
5304
5305 cleanup_layout(_metadata);
5306 }
5307
FIXTURE_VARIANT(layout4_disconnected_leafs)5308 FIXTURE_VARIANT(layout4_disconnected_leafs)
5309 {
5310 /*
5311 * Parent of the bind mount source. It should always be ignored when
5312 * testing against files under the s1d41 or s1d42 disconnected directories.
5313 */
5314 const __u64 allowed_s1d1;
5315 /*
5316 * Source of bind mount (to s2d2). It should always be enforced when
5317 * testing against files under the s1d41 or s1d42 disconnected directories.
5318 */
5319 const __u64 allowed_s1d2;
5320 /*
5321 * Original parent of s1d41. It should always be ignored when testing
5322 * against files under the s1d41 disconnected directory.
5323 */
5324 const __u64 allowed_s1d31;
5325 /*
5326 * Original parent of s1d42. It should always be ignored when testing
5327 * against files under the s1d42 disconnected directory.
5328 */
5329 const __u64 allowed_s1d32;
5330 /*
5331 * Opened and disconnected source directory. It should always be enforced
5332 * when testing against files under the s1d41 disconnected directory.
5333 */
5334 const __u64 allowed_s1d41;
5335 /*
5336 * Opened and disconnected source directory. It should always be enforced
5337 * when testing against files under the s1d42 disconnected directory.
5338 */
5339 const __u64 allowed_s1d42;
5340 /*
5341 * File in the s1d41 disconnected directory. It should always be enforced
5342 * when testing against itself under the s1d41 disconnected directory.
5343 */
5344 const __u64 allowed_f1;
5345 /*
5346 * File in the s1d41 disconnected directory. It should always be enforced
5347 * when testing against itself under the s1d41 disconnected directory.
5348 */
5349 const __u64 allowed_f2;
5350 /*
5351 * File in the s1d42 disconnected directory. It should always be enforced
5352 * when testing against itself under the s1d42 disconnected directory.
5353 */
5354 const __u64 allowed_f3;
5355 /*
5356 * Parent of the bind mount destination. It should always be enforced when
5357 * testing against files under the s1d41 or s1d42 disconnected directories.
5358 */
5359 const __u64 allowed_s2d1;
5360 /*
5361 * Directory covered by the bind mount. It should always be ignored when
5362 * testing against files under the s1d41 or s1d42 disconnected directories.
5363 */
5364 const __u64 allowed_s2d2;
5365 /*
5366 * New parent of the renamed s1d41. It should always be ignored when
5367 * testing against files under the s1d41 disconnected directory.
5368 */
5369 const __u64 allowed_s3d1;
5370 /*
5371 * New parent of the renamed s1d42. It should always be ignored when
5372 * testing against files under the s1d42 disconnected directory.
5373 */
5374 const __u64 allowed_s4d1;
5375
5376 /* Expected result of the call to open([fd:s1d41]/f1, O_RDONLY). */
5377 const int expected_read_result;
5378 /* Expected result of the call to renameat([fd:s1d41]/f1, [fd:s1d42]/f1). */
5379 const int expected_rename_result;
5380 /*
5381 * Expected result of the call to renameat([fd:s1d41]/f2, [fd:s1d42]/f3,
5382 * RENAME_EXCHANGE).
5383 */
5384 const int expected_exchange_result;
5385 /* Expected result of the call to renameat([fd:s1d42]/f4, [fd:s1d42]/f5). */
5386 const int expected_same_dir_rename_result;
5387 };
5388
5389 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d1_mount_src_parent)5390 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d1_mount_src_parent) {
5391 /* clang-format on */
5392 .allowed_s1d1 = LANDLOCK_ACCESS_FS_REFER |
5393 LANDLOCK_ACCESS_FS_READ_FILE |
5394 LANDLOCK_ACCESS_FS_EXECUTE |
5395 LANDLOCK_ACCESS_FS_MAKE_REG,
5396 .expected_read_result = EACCES,
5397 .expected_same_dir_rename_result = EACCES,
5398 .expected_rename_result = EACCES,
5399 .expected_exchange_result = EACCES,
5400 };
5401
5402 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d2_mount_src_refer)5403 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_refer) {
5404 /* clang-format on */
5405 .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5406 .expected_read_result = 0,
5407 .expected_same_dir_rename_result = EACCES,
5408 .expected_rename_result = EACCES,
5409 .expected_exchange_result = EACCES,
5410 };
5411
5412 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d2_mount_src_create)5413 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_create) {
5414 /* clang-format on */
5415 .allowed_s1d2 = LANDLOCK_ACCESS_FS_READ_FILE |
5416 LANDLOCK_ACCESS_FS_MAKE_REG,
5417 .expected_read_result = 0,
5418 .expected_same_dir_rename_result = 0,
5419 .expected_rename_result = EXDEV,
5420 .expected_exchange_result = EXDEV,
5421 };
5422
5423 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d2_mount_src_rename)5424 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_rename) {
5425 /* clang-format on */
5426 .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5427 .expected_read_result = EACCES,
5428 .expected_same_dir_rename_result = 0,
5429 .expected_rename_result = 0,
5430 .expected_exchange_result = 0,
5431 };
5432
5433 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d31_s1d32_old_parent)5434 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d31_s1d32_old_parent) {
5435 /* clang-format on */
5436 .allowed_s1d31 = LANDLOCK_ACCESS_FS_REFER |
5437 LANDLOCK_ACCESS_FS_READ_FILE |
5438 LANDLOCK_ACCESS_FS_EXECUTE |
5439 LANDLOCK_ACCESS_FS_MAKE_REG,
5440 .allowed_s1d32 = LANDLOCK_ACCESS_FS_REFER |
5441 LANDLOCK_ACCESS_FS_READ_FILE |
5442 LANDLOCK_ACCESS_FS_EXECUTE |
5443 LANDLOCK_ACCESS_FS_MAKE_REG,
5444 .expected_read_result = EACCES,
5445 .expected_same_dir_rename_result = EACCES,
5446 .expected_rename_result = EACCES,
5447 .expected_exchange_result = EACCES,
5448 };
5449
5450 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d41_s1d42_disconnected_refer)5451 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_refer) {
5452 /* clang-format on */
5453 .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER |
5454 LANDLOCK_ACCESS_FS_READ_FILE,
5455 .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER |
5456 LANDLOCK_ACCESS_FS_READ_FILE,
5457 .expected_read_result = 0,
5458 .expected_same_dir_rename_result = EACCES,
5459 .expected_rename_result = EACCES,
5460 .expected_exchange_result = EACCES,
5461 };
5462
5463 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d41_s1d42_disconnected_create)5464 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_create) {
5465 /* clang-format on */
5466 .allowed_s1d41 = LANDLOCK_ACCESS_FS_READ_FILE |
5467 LANDLOCK_ACCESS_FS_MAKE_REG,
5468 .allowed_s1d42 = LANDLOCK_ACCESS_FS_READ_FILE |
5469 LANDLOCK_ACCESS_FS_MAKE_REG,
5470 .expected_read_result = 0,
5471 .expected_same_dir_rename_result = 0,
5472 .expected_rename_result = EXDEV,
5473 .expected_exchange_result = EXDEV,
5474 };
5475
5476 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d41_s1d42_disconnected_rename_even)5477 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_even) {
5478 /* clang-format on */
5479 .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5480 .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5481 .expected_read_result = EACCES,
5482 .expected_same_dir_rename_result = 0,
5483 .expected_rename_result = 0,
5484 .expected_exchange_result = 0,
5485 };
5486
5487 /* The destination directory has more access right. */
5488 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d41_s1d42_disconnected_rename_more)5489 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_more) {
5490 /* clang-format on */
5491 .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5492 .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER |
5493 LANDLOCK_ACCESS_FS_MAKE_REG |
5494 LANDLOCK_ACCESS_FS_EXECUTE,
5495 .expected_read_result = EACCES,
5496 .expected_same_dir_rename_result = 0,
5497 /* Access denied. */
5498 .expected_rename_result = EXDEV,
5499 .expected_exchange_result = EXDEV,
5500 };
5501
5502 /* The destination directory has less access right. */
5503 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s1d41_s1d42_disconnected_rename_less)5504 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_less) {
5505 /* clang-format on */
5506 .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER |
5507 LANDLOCK_ACCESS_FS_MAKE_REG |
5508 LANDLOCK_ACCESS_FS_EXECUTE,
5509 .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5510 .expected_read_result = EACCES,
5511 .expected_same_dir_rename_result = 0,
5512 /* Access allowed. */
5513 .expected_rename_result = 0,
5514 .expected_exchange_result = EXDEV,
5515 };
5516
5517 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s2d1_mount_dst_parent_create)5518 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_create) {
5519 /* clang-format on */
5520 .allowed_s2d1 = LANDLOCK_ACCESS_FS_READ_FILE |
5521 LANDLOCK_ACCESS_FS_MAKE_REG,
5522 .expected_read_result = 0,
5523 .expected_same_dir_rename_result = 0,
5524 .expected_rename_result = EXDEV,
5525 .expected_exchange_result = EXDEV,
5526 };
5527
5528 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s2d1_mount_dst_parent_refer)5529 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_refer) {
5530 /* clang-format on */
5531 .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5532 .expected_read_result = 0,
5533 .expected_same_dir_rename_result = EACCES,
5534 .expected_rename_result = EACCES,
5535 .expected_exchange_result = EACCES,
5536 };
5537
5538 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s2d1_mount_dst_parent_mini)5539 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_mini) {
5540 /* clang-format on */
5541 .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER |
5542 LANDLOCK_ACCESS_FS_READ_FILE |
5543 LANDLOCK_ACCESS_FS_MAKE_REG,
5544 .expected_read_result = 0,
5545 .expected_same_dir_rename_result = 0,
5546 .expected_rename_result = 0,
5547 .expected_exchange_result = 0,
5548 };
5549
5550 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s2d2_covered_by_mount)5551 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d2_covered_by_mount) {
5552 /* clang-format on */
5553 .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER |
5554 LANDLOCK_ACCESS_FS_READ_FILE |
5555 LANDLOCK_ACCESS_FS_EXECUTE |
5556 LANDLOCK_ACCESS_FS_MAKE_REG,
5557 .expected_read_result = EACCES,
5558 .expected_same_dir_rename_result = EACCES,
5559 .expected_rename_result = EACCES,
5560 .expected_exchange_result = EACCES,
5561 };
5562
5563 /* Tests collect_domain_accesses(). */
5564 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s3d1_s4d1_new_parent_refer)5565 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_new_parent_refer) {
5566 /* clang-format on */
5567 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5568 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5569 .expected_read_result = 0,
5570 .expected_same_dir_rename_result = EACCES,
5571 .expected_rename_result = EACCES,
5572 .expected_exchange_result = EACCES,
5573 };
5574
5575 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s3d1_s4d1_new_parent_create)5576 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_new_parent_create) {
5577 /* clang-format on */
5578 .allowed_s3d1 = LANDLOCK_ACCESS_FS_READ_FILE |
5579 LANDLOCK_ACCESS_FS_MAKE_REG,
5580 .allowed_s4d1 = LANDLOCK_ACCESS_FS_READ_FILE |
5581 LANDLOCK_ACCESS_FS_MAKE_REG,
5582 .expected_read_result = 0,
5583 .expected_same_dir_rename_result = 0,
5584 .expected_rename_result = EXDEV,
5585 .expected_exchange_result = EXDEV,
5586 };
5587
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s3d1_s4d1_disconnected_rename_even)5588 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,
5589 s3d1_s4d1_disconnected_rename_even){
5590 /* clang-format on */
5591 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5592 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5593 .expected_read_result = EACCES,
5594 .expected_same_dir_rename_result = 0,
5595 .expected_rename_result = 0,
5596 .expected_exchange_result = 0,
5597 };
5598
5599 /* The destination directory has more access right. */
5600 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s3d1_s4d1_disconnected_rename_more)5601 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_disconnected_rename_more) {
5602 /* clang-format on */
5603 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5604 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG |
5605 LANDLOCK_ACCESS_FS_EXECUTE,
5606 .expected_read_result = EACCES,
5607 .expected_same_dir_rename_result = 0,
5608 /* Access denied. */
5609 .expected_rename_result = EXDEV,
5610 .expected_exchange_result = EXDEV,
5611 };
5612
5613 /* The destination directory has less access right. */
5614 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,s3d1_s4d1_disconnected_rename_less)5615 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_disconnected_rename_less) {
5616 /* clang-format on */
5617 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG |
5618 LANDLOCK_ACCESS_FS_EXECUTE,
5619 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5620 .expected_read_result = EACCES,
5621 .expected_same_dir_rename_result = 0,
5622 /* Access allowed. */
5623 .expected_rename_result = 0,
5624 .expected_exchange_result = EXDEV,
5625 };
5626
5627 /* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,f1_f2_f3)5628 FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, f1_f2_f3) {
5629 /* clang-format on */
5630 .allowed_f1 = LANDLOCK_ACCESS_FS_READ_FILE,
5631 .allowed_f2 = LANDLOCK_ACCESS_FS_READ_FILE,
5632 .allowed_f3 = LANDLOCK_ACCESS_FS_READ_FILE,
5633 .expected_read_result = 0,
5634 .expected_same_dir_rename_result = EACCES,
5635 .expected_rename_result = EACCES,
5636 .expected_exchange_result = EACCES,
5637 };
5638
TEST_F_FORK(layout4_disconnected_leafs,read_rename_exchange)5639 TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange)
5640 {
5641 const __u64 handled_access =
5642 LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE |
5643 LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_MAKE_REG;
5644 const struct rule rules[] = {
5645 {
5646 .path = TMP_DIR "/s1d1",
5647 .access = variant->allowed_s1d1,
5648 },
5649 {
5650 .path = TMP_DIR "/s1d1/s1d2",
5651 .access = variant->allowed_s1d2,
5652 },
5653 {
5654 .path = TMP_DIR "/s1d1/s1d2/s1d31",
5655 .access = variant->allowed_s1d31,
5656 },
5657 {
5658 .path = TMP_DIR "/s1d1/s1d2/s1d32",
5659 .access = variant->allowed_s1d32,
5660 },
5661 {
5662 .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41",
5663 .access = variant->allowed_s1d41,
5664 },
5665 {
5666 .path = TMP_DIR "/s1d1/s1d2/s1d32/s1d42",
5667 .access = variant->allowed_s1d42,
5668 },
5669 {
5670 .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f1",
5671 .access = variant->allowed_f1,
5672 },
5673 {
5674 .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f2",
5675 .access = variant->allowed_f2,
5676 },
5677 {
5678 .path = TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f3",
5679 .access = variant->allowed_f3,
5680 },
5681 {
5682 .path = TMP_DIR "/s2d1",
5683 .access = variant->allowed_s2d1,
5684 },
5685 /* s2d2_fd */
5686 {
5687 .path = TMP_DIR "/s3d1",
5688 .access = variant->allowed_s3d1,
5689 },
5690 {
5691 .path = TMP_DIR "/s4d1",
5692 .access = variant->allowed_s4d1,
5693 },
5694 {},
5695 };
5696 int ruleset_fd, s1d41_bind_fd, s1d42_bind_fd;
5697
5698 ruleset_fd = create_ruleset(_metadata, handled_access, rules);
5699 ASSERT_LE(0, ruleset_fd);
5700
5701 /* Adds rule for the covered directory. */
5702 if (variant->allowed_s2d2) {
5703 ASSERT_EQ(0, landlock_add_rule(
5704 ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
5705 &(struct landlock_path_beneath_attr){
5706 .parent_fd = self->s2d2_fd,
5707 .allowed_access =
5708 variant->allowed_s2d2,
5709 },
5710 0));
5711 }
5712 EXPECT_EQ(0, close(self->s2d2_fd));
5713
5714 s1d41_bind_fd = open(TMP_DIR "/s2d1/s2d2/s1d31/s1d41",
5715 O_DIRECTORY | O_PATH | O_CLOEXEC);
5716 ASSERT_LE(0, s1d41_bind_fd);
5717 s1d42_bind_fd = open(TMP_DIR "/s2d1/s2d2/s1d32/s1d42",
5718 O_DIRECTORY | O_PATH | O_CLOEXEC);
5719 ASSERT_LE(0, s1d42_bind_fd);
5720
5721 /* Disconnects and checks source and destination directories. */
5722 EXPECT_EQ(0, test_open_rel(s1d41_bind_fd, "..", O_DIRECTORY));
5723 EXPECT_EQ(0, test_open_rel(s1d42_bind_fd, "..", O_DIRECTORY));
5724 /* Renames to make it accessible through s3d1/s1d41 */
5725 ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s1d1/s1d2/s1d31/s1d41",
5726 AT_FDCWD, TMP_DIR "/s3d1/s1d41"));
5727 /* Renames to make it accessible through s4d1/s1d42 */
5728 ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s1d1/s1d2/s1d32/s1d42",
5729 AT_FDCWD, TMP_DIR "/s4d1/s1d42"));
5730 EXPECT_EQ(ENOENT, test_open_rel(s1d41_bind_fd, "..", O_DIRECTORY));
5731 EXPECT_EQ(ENOENT, test_open_rel(s1d42_bind_fd, "..", O_DIRECTORY));
5732
5733 enforce_ruleset(_metadata, ruleset_fd);
5734 EXPECT_EQ(0, close(ruleset_fd));
5735
5736 EXPECT_EQ(variant->expected_read_result,
5737 test_open_rel(s1d41_bind_fd, "f1", O_RDONLY));
5738
5739 EXPECT_EQ(variant->expected_rename_result,
5740 test_renameat(s1d41_bind_fd, "f1", s1d42_bind_fd, "f1"));
5741 EXPECT_EQ(variant->expected_exchange_result,
5742 test_exchangeat(s1d41_bind_fd, "f2", s1d42_bind_fd, "f3"));
5743
5744 EXPECT_EQ(variant->expected_same_dir_rename_result,
5745 test_renameat(s1d42_bind_fd, "f4", s1d42_bind_fd, "f5"));
5746 }
5747
5748 /*
5749 * layout5_disconnected_branch before rename:
5750 *
5751 * tmp
5752 * ├── s1d1
5753 * │ └── s1d2 [source of the first bind mount]
5754 * │ └── s1d3
5755 * │ ├── s1d41
5756 * │ │ ├── f1
5757 * │ │ └── f2
5758 * │ └── s1d42
5759 * │ ├── f3
5760 * │ └── f4
5761 * ├── s2d1
5762 * │ └── s2d2 [source of the second bind mount]
5763 * │ └── s2d3
5764 * │ └── s2d4 [first s1d2 bind mount]
5765 * │ └── s1d3
5766 * │ ├── s1d41
5767 * │ │ ├── f1
5768 * │ │ └── f2
5769 * │ └── s1d42
5770 * │ ├── f3
5771 * │ └── f4
5772 * ├── s3d1
5773 * │ └── s3d2 [second s2d2 bind mount]
5774 * │ └── s2d3
5775 * │ └── s2d4 [first s1d2 bind mount]
5776 * │ └── s1d3
5777 * │ ├── s1d41
5778 * │ │ ├── f1
5779 * │ │ └── f2
5780 * │ └── s1d42
5781 * │ ├── f3
5782 * │ └── f4
5783 * └── s4d1
5784 *
5785 * After rename:
5786 *
5787 * tmp
5788 * ├── s1d1
5789 * │ └── s1d2 [source of the first bind mount]
5790 * │ └── s1d3
5791 * │ ├── s1d41
5792 * │ │ ├── f1
5793 * │ │ └── f2
5794 * │ └── s1d42
5795 * │ ├── f3
5796 * │ └── f4
5797 * ├── s2d1
5798 * │ └── s2d2 [source of the second bind mount]
5799 * ├── s3d1
5800 * │ └── s3d2 [second s2d2 bind mount]
5801 * └── s4d1
5802 * └── s2d3 [renamed here]
5803 * └── s2d4 [first s1d2 bind mount]
5804 * └── s1d3
5805 * ├── s1d41
5806 * │ ├── f1
5807 * │ └── f2
5808 * └── s1d42
5809 * ├── f3
5810 * └── f4
5811 *
5812 * Decision path for access from the s3d1/s3d2/s2d3/s2d4/s1d3 file descriptor:
5813 * 1. first bind mount: s1d3 -> s1d2
5814 * 2. second bind mount: s2d3
5815 * 3. tmp mount: s4d1 -> tmp [disconnected branch]
5816 * 4. second bind mount: s2d2
5817 * 5. tmp mount: s3d1 -> tmp
5818 * 6. parent mounts: [...] -> /
5819 *
5820 * The s4d1 directory is evaluated even if it is not in the s2d2 mount.
5821 */
5822
5823 /* clang-format off */
FIXTURE(layout5_disconnected_branch)5824 FIXTURE(layout5_disconnected_branch) {
5825 int s2d4_fd, s3d2_fd;
5826 };
5827 /* clang-format on */
5828
FIXTURE_SETUP(layout5_disconnected_branch)5829 FIXTURE_SETUP(layout5_disconnected_branch)
5830 {
5831 prepare_layout(_metadata);
5832
5833 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f1");
5834 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f2");
5835 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f3");
5836 create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f4");
5837 create_directory(_metadata, TMP_DIR "/s2d1/s2d2/s2d3/s2d4");
5838 create_directory(_metadata, TMP_DIR "/s3d1/s3d2");
5839 create_directory(_metadata, TMP_DIR "/s4d1");
5840
5841 self->s2d4_fd = open(TMP_DIR "/s2d1/s2d2/s2d3/s2d4",
5842 O_DIRECTORY | O_PATH | O_CLOEXEC);
5843 ASSERT_LE(0, self->s2d4_fd);
5844
5845 self->s3d2_fd =
5846 open(TMP_DIR "/s3d1/s3d2", O_DIRECTORY | O_PATH | O_CLOEXEC);
5847 ASSERT_LE(0, self->s3d2_fd);
5848
5849 set_cap(_metadata, CAP_SYS_ADMIN);
5850 ASSERT_EQ(0, mount(TMP_DIR "/s1d1/s1d2", TMP_DIR "/s2d1/s2d2/s2d3/s2d4",
5851 NULL, MS_BIND, NULL));
5852 ASSERT_EQ(0, mount(TMP_DIR "/s2d1/s2d2", TMP_DIR "/s3d1/s3d2", NULL,
5853 MS_BIND | MS_REC, NULL));
5854 clear_cap(_metadata, CAP_SYS_ADMIN);
5855 }
5856
FIXTURE_TEARDOWN_PARENT(layout5_disconnected_branch)5857 FIXTURE_TEARDOWN_PARENT(layout5_disconnected_branch)
5858 {
5859 /* Bind mounts are handled by namespace lifetime. */
5860
5861 /* Removes files after renames. */
5862 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f1");
5863 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f2");
5864 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f1");
5865 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f3");
5866 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f4");
5867 remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f5");
5868
5869 cleanup_layout(_metadata);
5870 }
5871
FIXTURE_VARIANT(layout5_disconnected_branch)5872 FIXTURE_VARIANT(layout5_disconnected_branch)
5873 {
5874 /*
5875 * Parent of all files. It should always be enforced when testing against
5876 * files under the s1d41 or s1d42 disconnected directories.
5877 */
5878 const __u64 allowed_base;
5879 /*
5880 * Parent of the first bind mount source. It should always be ignored when
5881 * testing against files under the s1d41 or s1d42 disconnected directories.
5882 */
5883 const __u64 allowed_s1d1;
5884 const __u64 allowed_s1d2;
5885 const __u64 allowed_s1d3;
5886 const __u64 allowed_s2d1;
5887 const __u64 allowed_s2d2;
5888 const __u64 allowed_s2d3;
5889 const __u64 allowed_s2d4;
5890 const __u64 allowed_s3d1;
5891 const __u64 allowed_s3d2;
5892 const __u64 allowed_s4d1;
5893
5894 /* Expected result of the call to open([fd:s1d3]/s1d41/f1, O_RDONLY). */
5895 const int expected_read_result;
5896 /*
5897 * Expected result of the call to renameat([fd:s1d3]/s1d41/f1,
5898 * [fd:s1d3]/s1d42/f1).
5899 */
5900 const int expected_rename_result;
5901 /*
5902 * Expected result of the call to renameat([fd:s1d3]/s1d41/f2,
5903 * [fd:s1d3]/s1d42/f3, RENAME_EXCHANGE).
5904 */
5905 const int expected_exchange_result;
5906 /*
5907 * Expected result of the call to renameat([fd:s1d3]/s1d42/f4,
5908 * [fd:s1d3]/s1d42/f5).
5909 */
5910 const int expected_same_dir_rename_result;
5911 };
5912
5913 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d1_mount1_src_parent)5914 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d1_mount1_src_parent) {
5915 /* clang-format on */
5916 .allowed_s1d1 = LANDLOCK_ACCESS_FS_REFER |
5917 LANDLOCK_ACCESS_FS_READ_FILE |
5918 LANDLOCK_ACCESS_FS_EXECUTE |
5919 LANDLOCK_ACCESS_FS_MAKE_REG,
5920 .expected_read_result = EACCES,
5921 .expected_same_dir_rename_result = EACCES,
5922 .expected_rename_result = EACCES,
5923 .expected_exchange_result = EACCES,
5924 };
5925
5926 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d2_mount1_src_refer)5927 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_refer) {
5928 /* clang-format on */
5929 .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5930 .expected_read_result = 0,
5931 .expected_same_dir_rename_result = EACCES,
5932 .expected_rename_result = EACCES,
5933 .expected_exchange_result = EACCES,
5934 };
5935
5936 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d2_mount1_src_create)5937 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_create) {
5938 /* clang-format on */
5939 .allowed_s1d2 = LANDLOCK_ACCESS_FS_READ_FILE |
5940 LANDLOCK_ACCESS_FS_MAKE_REG,
5941 .expected_read_result = 0,
5942 .expected_same_dir_rename_result = 0,
5943 .expected_rename_result = EXDEV,
5944 .expected_exchange_result = EXDEV,
5945 };
5946
5947 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d2_mount1_src_rename)5948 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_rename) {
5949 /* clang-format on */
5950 .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5951 .expected_read_result = EACCES,
5952 .expected_same_dir_rename_result = 0,
5953 .expected_rename_result = 0,
5954 .expected_exchange_result = 0,
5955 };
5956
5957 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d3_fd_refer)5958 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_refer) {
5959 /* clang-format on */
5960 .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
5961 .expected_read_result = 0,
5962 .expected_same_dir_rename_result = EACCES,
5963 .expected_rename_result = EACCES,
5964 .expected_exchange_result = EACCES,
5965 };
5966
5967 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d3_fd_create)5968 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_create) {
5969 /* clang-format on */
5970 .allowed_s1d3 = LANDLOCK_ACCESS_FS_READ_FILE |
5971 LANDLOCK_ACCESS_FS_MAKE_REG,
5972 .expected_read_result = 0,
5973 .expected_same_dir_rename_result = 0,
5974 .expected_rename_result = EXDEV,
5975 .expected_exchange_result = EXDEV,
5976 };
5977
5978 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d3_fd_rename)5979 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_rename) {
5980 /* clang-format on */
5981 .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
5982 .expected_read_result = EACCES,
5983 .expected_same_dir_rename_result = 0,
5984 .expected_rename_result = 0,
5985 .expected_exchange_result = 0,
5986 };
5987
5988 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s1d3_fd_full)5989 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_full) {
5990 /* clang-format on */
5991 .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER |
5992 LANDLOCK_ACCESS_FS_READ_FILE |
5993 LANDLOCK_ACCESS_FS_EXECUTE |
5994 LANDLOCK_ACCESS_FS_MAKE_REG,
5995 .expected_read_result = 0,
5996 .expected_same_dir_rename_result = 0,
5997 .expected_rename_result = 0,
5998 .expected_exchange_result = 0,
5999 };
6000
6001 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d1_mount2_src_parent)6002 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d1_mount2_src_parent) {
6003 /* clang-format on */
6004 .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER |
6005 LANDLOCK_ACCESS_FS_READ_FILE |
6006 LANDLOCK_ACCESS_FS_EXECUTE |
6007 LANDLOCK_ACCESS_FS_MAKE_REG,
6008 .expected_read_result = EACCES,
6009 .expected_same_dir_rename_result = EACCES,
6010 .expected_rename_result = EACCES,
6011 .expected_exchange_result = EACCES,
6012 };
6013
6014 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d2_mount2_src_refer)6015 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_refer) {
6016 /* clang-format on */
6017 .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
6018 .expected_read_result = 0,
6019 .expected_same_dir_rename_result = EACCES,
6020 .expected_rename_result = EACCES,
6021 .expected_exchange_result = EACCES,
6022 };
6023
6024 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d2_mount2_src_create)6025 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_create) {
6026 /* clang-format on */
6027 .allowed_s2d2 = LANDLOCK_ACCESS_FS_READ_FILE |
6028 LANDLOCK_ACCESS_FS_MAKE_REG,
6029 .expected_read_result = 0,
6030 .expected_same_dir_rename_result = 0,
6031 .expected_rename_result = EXDEV,
6032 .expected_exchange_result = EXDEV,
6033 };
6034
6035 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d2_mount2_src_rename)6036 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_rename) {
6037 /* clang-format on */
6038 .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
6039 .expected_read_result = EACCES,
6040 .expected_same_dir_rename_result = 0,
6041 .expected_rename_result = 0,
6042 .expected_exchange_result = 0,
6043 };
6044
6045 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d3_mount1_dst_parent_refer)6046 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_refer) {
6047 /* clang-format on */
6048 .allowed_s2d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
6049 .expected_read_result = 0,
6050 .expected_same_dir_rename_result = EACCES,
6051 .expected_rename_result = EACCES,
6052 .expected_exchange_result = EACCES,
6053 };
6054
6055 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d3_mount1_dst_parent_create)6056 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_create) {
6057 /* clang-format on */
6058 .allowed_s2d3 = LANDLOCK_ACCESS_FS_READ_FILE |
6059 LANDLOCK_ACCESS_FS_MAKE_REG,
6060 .expected_read_result = 0,
6061 .expected_same_dir_rename_result = 0,
6062 .expected_rename_result = EXDEV,
6063 .expected_exchange_result = EXDEV,
6064 };
6065
6066 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d3_mount1_dst_parent_rename)6067 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_rename) {
6068 /* clang-format on */
6069 .allowed_s2d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
6070 .expected_read_result = EACCES,
6071 .expected_same_dir_rename_result = 0,
6072 .expected_rename_result = 0,
6073 .expected_exchange_result = 0,
6074 };
6075
6076 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s2d4_mount1_dst)6077 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d4_mount1_dst) {
6078 /* clang-format on */
6079 .allowed_s2d4 = LANDLOCK_ACCESS_FS_REFER |
6080 LANDLOCK_ACCESS_FS_READ_FILE |
6081 LANDLOCK_ACCESS_FS_EXECUTE |
6082 LANDLOCK_ACCESS_FS_MAKE_REG,
6083 .expected_read_result = EACCES,
6084 .expected_same_dir_rename_result = EACCES,
6085 .expected_rename_result = EACCES,
6086 .expected_exchange_result = EACCES,
6087 };
6088
6089 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s3d1_mount2_dst_parent_refer)6090 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_refer) {
6091 /* clang-format on */
6092 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
6093 .expected_read_result = 0,
6094 .expected_same_dir_rename_result = EACCES,
6095 .expected_rename_result = EACCES,
6096 .expected_exchange_result = EACCES,
6097 };
6098
6099 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s3d1_mount2_dst_parent_create)6100 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_create) {
6101 /* clang-format on */
6102 .allowed_s3d1 = LANDLOCK_ACCESS_FS_READ_FILE |
6103 LANDLOCK_ACCESS_FS_MAKE_REG,
6104 .expected_read_result = 0,
6105 .expected_same_dir_rename_result = 0,
6106 .expected_rename_result = EXDEV,
6107 .expected_exchange_result = EXDEV,
6108 };
6109
6110 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s3d1_mount2_dst_parent_rename)6111 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_rename) {
6112 /* clang-format on */
6113 .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
6114 .expected_read_result = EACCES,
6115 .expected_same_dir_rename_result = 0,
6116 .expected_rename_result = 0,
6117 .expected_exchange_result = 0,
6118 };
6119
6120 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s3d2_mount1_dst)6121 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d2_mount1_dst) {
6122 /* clang-format on */
6123 .allowed_s3d2 = LANDLOCK_ACCESS_FS_REFER |
6124 LANDLOCK_ACCESS_FS_READ_FILE |
6125 LANDLOCK_ACCESS_FS_EXECUTE |
6126 LANDLOCK_ACCESS_FS_MAKE_REG,
6127 .expected_read_result = EACCES,
6128 .expected_same_dir_rename_result = EACCES,
6129 .expected_rename_result = EACCES,
6130 .expected_exchange_result = EACCES,
6131 };
6132
6133 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s4d1_rename_parent_refer)6134 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_refer) {
6135 /* clang-format on */
6136 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
6137 .expected_read_result = 0,
6138 .expected_same_dir_rename_result = EACCES,
6139 .expected_rename_result = EACCES,
6140 .expected_exchange_result = EACCES,
6141 };
6142
6143 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s4d1_rename_parent_create)6144 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_create) {
6145 /* clang-format on */
6146 .allowed_s4d1 = LANDLOCK_ACCESS_FS_READ_FILE |
6147 LANDLOCK_ACCESS_FS_MAKE_REG,
6148 .expected_read_result = 0,
6149 .expected_same_dir_rename_result = 0,
6150 .expected_rename_result = EXDEV,
6151 .expected_exchange_result = EXDEV,
6152 };
6153
6154 /* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch,s4d1_rename_parent_rename)6155 FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_rename) {
6156 /* clang-format on */
6157 .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
6158 .expected_read_result = EACCES,
6159 .expected_same_dir_rename_result = 0,
6160 .expected_rename_result = 0,
6161 .expected_exchange_result = 0,
6162 };
6163
TEST_F_FORK(layout5_disconnected_branch,read_rename_exchange)6164 TEST_F_FORK(layout5_disconnected_branch, read_rename_exchange)
6165 {
6166 const __u64 handled_access =
6167 LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE |
6168 LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_MAKE_REG;
6169 const struct rule rules[] = {
6170 {
6171 .path = TMP_DIR "/s1d1",
6172 .access = variant->allowed_s1d1,
6173 },
6174 {
6175 .path = TMP_DIR "/s1d1/s1d2",
6176 .access = variant->allowed_s1d2,
6177 },
6178 {
6179 .path = TMP_DIR "/s1d1/s1d2/s1d3",
6180 .access = variant->allowed_s1d3,
6181 },
6182 {
6183 .path = TMP_DIR "/s2d1",
6184 .access = variant->allowed_s2d1,
6185 },
6186 {
6187 .path = TMP_DIR "/s2d1/s2d2",
6188 .access = variant->allowed_s2d2,
6189 },
6190 {
6191 .path = TMP_DIR "/s2d1/s2d2/s2d3",
6192 .access = variant->allowed_s2d3,
6193 },
6194 /* s2d4_fd */
6195 {
6196 .path = TMP_DIR "/s3d1",
6197 .access = variant->allowed_s3d1,
6198 },
6199 /* s3d2_fd */
6200 {
6201 .path = TMP_DIR "/s4d1",
6202 .access = variant->allowed_s4d1,
6203 },
6204 {},
6205 };
6206 int ruleset_fd, s1d3_bind_fd;
6207
6208 ruleset_fd = create_ruleset(_metadata, handled_access, rules);
6209 ASSERT_LE(0, ruleset_fd);
6210
6211 /* Adds rules for the covered directories. */
6212 if (variant->allowed_s2d4) {
6213 ASSERT_EQ(0, landlock_add_rule(
6214 ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
6215 &(struct landlock_path_beneath_attr){
6216 .parent_fd = self->s2d4_fd,
6217 .allowed_access =
6218 variant->allowed_s2d4,
6219 },
6220 0));
6221 }
6222 EXPECT_EQ(0, close(self->s2d4_fd));
6223
6224 if (variant->allowed_s3d2) {
6225 ASSERT_EQ(0, landlock_add_rule(
6226 ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
6227 &(struct landlock_path_beneath_attr){
6228 .parent_fd = self->s3d2_fd,
6229 .allowed_access =
6230 variant->allowed_s3d2,
6231 },
6232 0));
6233 }
6234 EXPECT_EQ(0, close(self->s3d2_fd));
6235
6236 s1d3_bind_fd = open(TMP_DIR "/s3d1/s3d2/s2d3/s2d4/s1d3",
6237 O_DIRECTORY | O_PATH | O_CLOEXEC);
6238 ASSERT_LE(0, s1d3_bind_fd);
6239
6240 /* Disconnects and checks source and destination directories. */
6241 EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "..", O_DIRECTORY));
6242 EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "../..", O_DIRECTORY));
6243 /* Renames to make it accessible through s3d1/s1d41 */
6244 ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s2d1/s2d2/s2d3",
6245 AT_FDCWD, TMP_DIR "/s4d1/s2d3"));
6246 EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "..", O_DIRECTORY));
6247 EXPECT_EQ(ENOENT, test_open_rel(s1d3_bind_fd, "../..", O_DIRECTORY));
6248
6249 enforce_ruleset(_metadata, ruleset_fd);
6250 EXPECT_EQ(0, close(ruleset_fd));
6251
6252 EXPECT_EQ(variant->expected_read_result,
6253 test_open_rel(s1d3_bind_fd, "s1d41/f1", O_RDONLY));
6254
6255 EXPECT_EQ(variant->expected_rename_result,
6256 test_renameat(s1d3_bind_fd, "s1d41/f1", s1d3_bind_fd,
6257 "s1d42/f1"));
6258 EXPECT_EQ(variant->expected_exchange_result,
6259 test_exchangeat(s1d3_bind_fd, "s1d41/f2", s1d3_bind_fd,
6260 "s1d42/f3"));
6261
6262 EXPECT_EQ(variant->expected_same_dir_rename_result,
6263 test_renameat(s1d3_bind_fd, "s1d42/f4", s1d3_bind_fd,
6264 "s1d42/f5"));
6265 }
6266
6267 #define LOWER_BASE TMP_DIR "/lower"
6268 #define LOWER_DATA LOWER_BASE "/data"
6269 static const char lower_fl1[] = LOWER_DATA "/fl1";
6270 static const char lower_dl1[] = LOWER_DATA "/dl1";
6271 static const char lower_dl1_fl2[] = LOWER_DATA "/dl1/fl2";
6272 static const char lower_fo1[] = LOWER_DATA "/fo1";
6273 static const char lower_do1[] = LOWER_DATA "/do1";
6274 static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2";
6275 static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3";
6276
6277 static const char (*lower_base_files[])[] = {
6278 &lower_fl1,
6279 &lower_fo1,
6280 NULL,
6281 };
6282 static const char (*lower_base_directories[])[] = {
6283 &lower_dl1,
6284 &lower_do1,
6285 NULL,
6286 };
6287 static const char (*lower_sub_files[])[] = {
6288 &lower_dl1_fl2,
6289 &lower_do1_fo2,
6290 &lower_do1_fl3,
6291 NULL,
6292 };
6293
6294 #define UPPER_BASE TMP_DIR "/upper"
6295 #define UPPER_DATA UPPER_BASE "/data"
6296 #define UPPER_WORK UPPER_BASE "/work"
6297 static const char upper_fu1[] = UPPER_DATA "/fu1";
6298 static const char upper_du1[] = UPPER_DATA "/du1";
6299 static const char upper_du1_fu2[] = UPPER_DATA "/du1/fu2";
6300 static const char upper_fo1[] = UPPER_DATA "/fo1";
6301 static const char upper_do1[] = UPPER_DATA "/do1";
6302 static const char upper_do1_fo2[] = UPPER_DATA "/do1/fo2";
6303 static const char upper_do1_fu3[] = UPPER_DATA "/do1/fu3";
6304
6305 static const char (*upper_base_files[])[] = {
6306 &upper_fu1,
6307 &upper_fo1,
6308 NULL,
6309 };
6310 static const char (*upper_base_directories[])[] = {
6311 &upper_du1,
6312 &upper_do1,
6313 NULL,
6314 };
6315 static const char (*upper_sub_files[])[] = {
6316 &upper_du1_fu2,
6317 &upper_do1_fo2,
6318 &upper_do1_fu3,
6319 NULL,
6320 };
6321
6322 #define MERGE_BASE TMP_DIR "/merge"
6323 #define MERGE_DATA MERGE_BASE "/data"
6324 static const char merge_fl1[] = MERGE_DATA "/fl1";
6325 static const char merge_dl1[] = MERGE_DATA "/dl1";
6326 static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2";
6327 static const char merge_fu1[] = MERGE_DATA "/fu1";
6328 static const char merge_du1[] = MERGE_DATA "/du1";
6329 static const char merge_du1_fu2[] = MERGE_DATA "/du1/fu2";
6330 static const char merge_fo1[] = MERGE_DATA "/fo1";
6331 static const char merge_do1[] = MERGE_DATA "/do1";
6332 static const char merge_do1_fo2[] = MERGE_DATA "/do1/fo2";
6333 static const char merge_do1_fl3[] = MERGE_DATA "/do1/fl3";
6334 static const char merge_do1_fu3[] = MERGE_DATA "/do1/fu3";
6335
6336 static const char (*merge_base_files[])[] = {
6337 &merge_fl1,
6338 &merge_fu1,
6339 &merge_fo1,
6340 NULL,
6341 };
6342 static const char (*merge_base_directories[])[] = {
6343 &merge_dl1,
6344 &merge_du1,
6345 &merge_do1,
6346 NULL,
6347 };
6348 static const char (*merge_sub_files[])[] = {
6349 &merge_dl1_fl2, &merge_du1_fu2, &merge_do1_fo2,
6350 &merge_do1_fl3, &merge_do1_fu3, NULL,
6351 };
6352
6353 /*
6354 * layout2_overlay hierarchy:
6355 *
6356 * tmp
6357 * ├── lower
6358 * │ └── data
6359 * │ ├── dl1
6360 * │ │ └── fl2
6361 * │ ├── do1
6362 * │ │ ├── fl3
6363 * │ │ └── fo2
6364 * │ ├── fl1
6365 * │ └── fo1
6366 * ├── merge
6367 * │ └── data
6368 * │ ├── dl1
6369 * │ │ └── fl2
6370 * │ ├── do1
6371 * │ │ ├── fl3
6372 * │ │ ├── fo2
6373 * │ │ └── fu3
6374 * │ ├── du1
6375 * │ │ └── fu2
6376 * │ ├── fl1
6377 * │ ├── fo1
6378 * │ └── fu1
6379 * └── upper
6380 * ├── data
6381 * │ ├── do1
6382 * │ │ ├── fo2
6383 * │ │ └── fu3
6384 * │ ├── du1
6385 * │ │ └── fu2
6386 * │ ├── fo1
6387 * │ └── fu1
6388 * └── work
6389 * └── work
6390 */
6391
FIXTURE(layout2_overlay)6392 FIXTURE(layout2_overlay)
6393 {
6394 bool skip_test;
6395 };
6396
FIXTURE_SETUP(layout2_overlay)6397 FIXTURE_SETUP(layout2_overlay)
6398 {
6399 if (!supports_filesystem("overlay")) {
6400 self->skip_test = true;
6401 SKIP(return, "overlayfs is not supported (setup)");
6402 }
6403
6404 prepare_layout(_metadata);
6405
6406 create_directory(_metadata, LOWER_BASE);
6407 set_cap(_metadata, CAP_SYS_ADMIN);
6408 /* Creates tmpfs mount points to get deterministic overlayfs. */
6409 ASSERT_EQ(0, mount_opt(&mnt_tmp, LOWER_BASE));
6410 clear_cap(_metadata, CAP_SYS_ADMIN);
6411 create_file(_metadata, lower_fl1);
6412 create_file(_metadata, lower_dl1_fl2);
6413 create_file(_metadata, lower_fo1);
6414 create_file(_metadata, lower_do1_fo2);
6415 create_file(_metadata, lower_do1_fl3);
6416
6417 create_directory(_metadata, UPPER_BASE);
6418 set_cap(_metadata, CAP_SYS_ADMIN);
6419 ASSERT_EQ(0, mount_opt(&mnt_tmp, UPPER_BASE));
6420 clear_cap(_metadata, CAP_SYS_ADMIN);
6421 create_file(_metadata, upper_fu1);
6422 create_file(_metadata, upper_du1_fu2);
6423 create_file(_metadata, upper_fo1);
6424 create_file(_metadata, upper_do1_fo2);
6425 create_file(_metadata, upper_do1_fu3);
6426 ASSERT_EQ(0, mkdir(UPPER_WORK, 0700));
6427
6428 create_directory(_metadata, MERGE_DATA);
6429 set_cap(_metadata, CAP_SYS_ADMIN);
6430 set_cap(_metadata, CAP_DAC_OVERRIDE);
6431 ASSERT_EQ(0, mount("overlay", MERGE_DATA, "overlay", 0,
6432 "lowerdir=" LOWER_DATA ",upperdir=" UPPER_DATA
6433 ",workdir=" UPPER_WORK));
6434 clear_cap(_metadata, CAP_DAC_OVERRIDE);
6435 clear_cap(_metadata, CAP_SYS_ADMIN);
6436 }
6437
FIXTURE_TEARDOWN_PARENT(layout2_overlay)6438 FIXTURE_TEARDOWN_PARENT(layout2_overlay)
6439 {
6440 if (self->skip_test)
6441 SKIP(return, "overlayfs is not supported (teardown)");
6442
6443 EXPECT_EQ(0, remove_path(lower_do1_fl3));
6444 EXPECT_EQ(0, remove_path(lower_dl1_fl2));
6445 EXPECT_EQ(0, remove_path(lower_fl1));
6446 EXPECT_EQ(0, remove_path(lower_do1_fo2));
6447 EXPECT_EQ(0, remove_path(lower_fo1));
6448
6449 /* umount(LOWER_BASE)) is handled by namespace lifetime. */
6450 EXPECT_EQ(0, remove_path(LOWER_BASE));
6451
6452 EXPECT_EQ(0, remove_path(upper_do1_fu3));
6453 EXPECT_EQ(0, remove_path(upper_du1_fu2));
6454 EXPECT_EQ(0, remove_path(upper_fu1));
6455 EXPECT_EQ(0, remove_path(upper_do1_fo2));
6456 EXPECT_EQ(0, remove_path(upper_fo1));
6457 EXPECT_EQ(0, remove_path(UPPER_WORK "/work"));
6458
6459 /* umount(UPPER_BASE)) is handled by namespace lifetime. */
6460 EXPECT_EQ(0, remove_path(UPPER_BASE));
6461
6462 /* umount(MERGE_DATA)) is handled by namespace lifetime. */
6463 EXPECT_EQ(0, remove_path(MERGE_DATA));
6464
6465 cleanup_layout(_metadata);
6466 }
6467
TEST_F_FORK(layout2_overlay,no_restriction)6468 TEST_F_FORK(layout2_overlay, no_restriction)
6469 {
6470 if (self->skip_test)
6471 SKIP(return, "overlayfs is not supported (test)");
6472
6473 ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY));
6474 ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY));
6475 ASSERT_EQ(0, test_open(lower_dl1_fl2, O_RDONLY));
6476 ASSERT_EQ(0, test_open(lower_fo1, O_RDONLY));
6477 ASSERT_EQ(0, test_open(lower_do1, O_RDONLY));
6478 ASSERT_EQ(0, test_open(lower_do1_fo2, O_RDONLY));
6479 ASSERT_EQ(0, test_open(lower_do1_fl3, O_RDONLY));
6480
6481 ASSERT_EQ(0, test_open(upper_fu1, O_RDONLY));
6482 ASSERT_EQ(0, test_open(upper_du1, O_RDONLY));
6483 ASSERT_EQ(0, test_open(upper_du1_fu2, O_RDONLY));
6484 ASSERT_EQ(0, test_open(upper_fo1, O_RDONLY));
6485 ASSERT_EQ(0, test_open(upper_do1, O_RDONLY));
6486 ASSERT_EQ(0, test_open(upper_do1_fo2, O_RDONLY));
6487 ASSERT_EQ(0, test_open(upper_do1_fu3, O_RDONLY));
6488
6489 ASSERT_EQ(0, test_open(merge_fl1, O_RDONLY));
6490 ASSERT_EQ(0, test_open(merge_dl1, O_RDONLY));
6491 ASSERT_EQ(0, test_open(merge_dl1_fl2, O_RDONLY));
6492 ASSERT_EQ(0, test_open(merge_fu1, O_RDONLY));
6493 ASSERT_EQ(0, test_open(merge_du1, O_RDONLY));
6494 ASSERT_EQ(0, test_open(merge_du1_fu2, O_RDONLY));
6495 ASSERT_EQ(0, test_open(merge_fo1, O_RDONLY));
6496 ASSERT_EQ(0, test_open(merge_do1, O_RDONLY));
6497 ASSERT_EQ(0, test_open(merge_do1_fo2, O_RDONLY));
6498 ASSERT_EQ(0, test_open(merge_do1_fl3, O_RDONLY));
6499 ASSERT_EQ(0, test_open(merge_do1_fu3, O_RDONLY));
6500 }
6501
6502 #define for_each_path(path_list, path_entry, i) \
6503 for (i = 0, path_entry = *path_list[i]; path_list[i]; \
6504 path_entry = *path_list[++i])
6505
TEST_F_FORK(layout2_overlay,same_content_different_file)6506 TEST_F_FORK(layout2_overlay, same_content_different_file)
6507 {
6508 /* Sets access right on parent directories of both layers. */
6509 const struct rule layer1_base[] = {
6510 {
6511 .path = LOWER_BASE,
6512 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6513 },
6514 {
6515 .path = UPPER_BASE,
6516 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6517 },
6518 {
6519 .path = MERGE_BASE,
6520 .access = ACCESS_RW,
6521 },
6522 {},
6523 };
6524 const struct rule layer2_data[] = {
6525 {
6526 .path = LOWER_DATA,
6527 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6528 },
6529 {
6530 .path = UPPER_DATA,
6531 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6532 },
6533 {
6534 .path = MERGE_DATA,
6535 .access = ACCESS_RW,
6536 },
6537 {},
6538 };
6539 /* Sets access right on directories inside both layers. */
6540 const struct rule layer3_subdirs[] = {
6541 {
6542 .path = lower_dl1,
6543 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6544 },
6545 {
6546 .path = lower_do1,
6547 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6548 },
6549 {
6550 .path = upper_du1,
6551 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6552 },
6553 {
6554 .path = upper_do1,
6555 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6556 },
6557 {
6558 .path = merge_dl1,
6559 .access = ACCESS_RW,
6560 },
6561 {
6562 .path = merge_du1,
6563 .access = ACCESS_RW,
6564 },
6565 {
6566 .path = merge_do1,
6567 .access = ACCESS_RW,
6568 },
6569 {},
6570 };
6571 /* Tighten access rights to the files. */
6572 const struct rule layer4_files[] = {
6573 {
6574 .path = lower_dl1_fl2,
6575 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6576 },
6577 {
6578 .path = lower_do1_fo2,
6579 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6580 },
6581 {
6582 .path = lower_do1_fl3,
6583 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6584 },
6585 {
6586 .path = upper_du1_fu2,
6587 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6588 },
6589 {
6590 .path = upper_do1_fo2,
6591 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6592 },
6593 {
6594 .path = upper_do1_fu3,
6595 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6596 },
6597 {
6598 .path = merge_dl1_fl2,
6599 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6600 LANDLOCK_ACCESS_FS_WRITE_FILE,
6601 },
6602 {
6603 .path = merge_du1_fu2,
6604 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6605 LANDLOCK_ACCESS_FS_WRITE_FILE,
6606 },
6607 {
6608 .path = merge_do1_fo2,
6609 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6610 LANDLOCK_ACCESS_FS_WRITE_FILE,
6611 },
6612 {
6613 .path = merge_do1_fl3,
6614 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6615 LANDLOCK_ACCESS_FS_WRITE_FILE,
6616 },
6617 {
6618 .path = merge_do1_fu3,
6619 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6620 LANDLOCK_ACCESS_FS_WRITE_FILE,
6621 },
6622 {},
6623 };
6624 const struct rule layer5_merge_only[] = {
6625 {
6626 .path = MERGE_DATA,
6627 .access = LANDLOCK_ACCESS_FS_READ_FILE |
6628 LANDLOCK_ACCESS_FS_WRITE_FILE,
6629 },
6630 {},
6631 };
6632 int ruleset_fd;
6633 size_t i;
6634 const char *path_entry;
6635
6636 if (self->skip_test)
6637 SKIP(return, "overlayfs is not supported (test)");
6638
6639 /* Sets rules on base directories (i.e. outside overlay scope). */
6640 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base);
6641 ASSERT_LE(0, ruleset_fd);
6642 enforce_ruleset(_metadata, ruleset_fd);
6643 ASSERT_EQ(0, close(ruleset_fd));
6644
6645 /* Checks lower layer. */
6646 for_each_path(lower_base_files, path_entry, i) {
6647 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6648 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6649 }
6650 for_each_path(lower_base_directories, path_entry, i) {
6651 ASSERT_EQ(EACCES,
6652 test_open(path_entry, O_RDONLY | O_DIRECTORY));
6653 }
6654 for_each_path(lower_sub_files, path_entry, i) {
6655 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6656 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6657 }
6658 /* Checks upper layer. */
6659 for_each_path(upper_base_files, path_entry, i) {
6660 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6661 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6662 }
6663 for_each_path(upper_base_directories, path_entry, i) {
6664 ASSERT_EQ(EACCES,
6665 test_open(path_entry, O_RDONLY | O_DIRECTORY));
6666 }
6667 for_each_path(upper_sub_files, path_entry, i) {
6668 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6669 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6670 }
6671 /*
6672 * Checks that access rights are independent from the lower and upper
6673 * layers: write access to upper files viewed through the merge point
6674 * is still allowed, and write access to lower file viewed (and copied)
6675 * through the merge point is still allowed.
6676 */
6677 for_each_path(merge_base_files, path_entry, i) {
6678 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6679 }
6680 for_each_path(merge_base_directories, path_entry, i) {
6681 ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY));
6682 }
6683 for_each_path(merge_sub_files, path_entry, i) {
6684 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6685 }
6686
6687 /* Sets rules on data directories (i.e. inside overlay scope). */
6688 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data);
6689 ASSERT_LE(0, ruleset_fd);
6690 enforce_ruleset(_metadata, ruleset_fd);
6691 ASSERT_EQ(0, close(ruleset_fd));
6692
6693 /* Checks merge. */
6694 for_each_path(merge_base_files, path_entry, i) {
6695 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6696 }
6697 for_each_path(merge_base_directories, path_entry, i) {
6698 ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY));
6699 }
6700 for_each_path(merge_sub_files, path_entry, i) {
6701 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6702 }
6703
6704 /* Same checks with tighter rules. */
6705 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs);
6706 ASSERT_LE(0, ruleset_fd);
6707 enforce_ruleset(_metadata, ruleset_fd);
6708 ASSERT_EQ(0, close(ruleset_fd));
6709
6710 /* Checks changes for lower layer. */
6711 for_each_path(lower_base_files, path_entry, i) {
6712 ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY));
6713 }
6714 /* Checks changes for upper layer. */
6715 for_each_path(upper_base_files, path_entry, i) {
6716 ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY));
6717 }
6718 /* Checks all merge accesses. */
6719 for_each_path(merge_base_files, path_entry, i) {
6720 ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR));
6721 }
6722 for_each_path(merge_base_directories, path_entry, i) {
6723 ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY));
6724 }
6725 for_each_path(merge_sub_files, path_entry, i) {
6726 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6727 }
6728
6729 /* Sets rules directly on overlayed files. */
6730 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files);
6731 ASSERT_LE(0, ruleset_fd);
6732 enforce_ruleset(_metadata, ruleset_fd);
6733 ASSERT_EQ(0, close(ruleset_fd));
6734
6735 /* Checks unchanged accesses on lower layer. */
6736 for_each_path(lower_sub_files, path_entry, i) {
6737 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6738 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6739 }
6740 /* Checks unchanged accesses on upper layer. */
6741 for_each_path(upper_sub_files, path_entry, i) {
6742 ASSERT_EQ(0, test_open(path_entry, O_RDONLY));
6743 ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY));
6744 }
6745 /* Checks all merge accesses. */
6746 for_each_path(merge_base_files, path_entry, i) {
6747 ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR));
6748 }
6749 for_each_path(merge_base_directories, path_entry, i) {
6750 ASSERT_EQ(EACCES,
6751 test_open(path_entry, O_RDONLY | O_DIRECTORY));
6752 }
6753 for_each_path(merge_sub_files, path_entry, i) {
6754 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6755 }
6756
6757 /* Only allowes access to the merge hierarchy. */
6758 ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only);
6759 ASSERT_LE(0, ruleset_fd);
6760 enforce_ruleset(_metadata, ruleset_fd);
6761 ASSERT_EQ(0, close(ruleset_fd));
6762
6763 /* Checks new accesses on lower layer. */
6764 for_each_path(lower_sub_files, path_entry, i) {
6765 ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY));
6766 }
6767 /* Checks new accesses on upper layer. */
6768 for_each_path(upper_sub_files, path_entry, i) {
6769 ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY));
6770 }
6771 /* Checks all merge accesses. */
6772 for_each_path(merge_base_files, path_entry, i) {
6773 ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR));
6774 }
6775 for_each_path(merge_base_directories, path_entry, i) {
6776 ASSERT_EQ(EACCES,
6777 test_open(path_entry, O_RDONLY | O_DIRECTORY));
6778 }
6779 for_each_path(merge_sub_files, path_entry, i) {
6780 ASSERT_EQ(0, test_open(path_entry, O_RDWR));
6781 }
6782 }
6783
FIXTURE(layout3_fs)6784 FIXTURE(layout3_fs)
6785 {
6786 bool has_created_dir;
6787 bool has_created_file;
6788 bool skip_test;
6789 };
6790
FIXTURE_VARIANT(layout3_fs)6791 FIXTURE_VARIANT(layout3_fs)
6792 {
6793 const struct mnt_opt mnt;
6794 const char *const file_path;
6795 unsigned int cwd_fs_magic;
6796 };
6797
6798 /* clang-format off */
FIXTURE_VARIANT_ADD(layout3_fs,tmpfs)6799 FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) {
6800 /* clang-format on */
6801 .mnt = {
6802 .type = "tmpfs",
6803 .data = MNT_TMP_DATA,
6804 },
6805 .file_path = file1_s1d1,
6806 };
6807
FIXTURE_VARIANT_ADD(layout3_fs,ramfs)6808 FIXTURE_VARIANT_ADD(layout3_fs, ramfs) {
6809 .mnt = {
6810 .type = "ramfs",
6811 .data = "mode=700",
6812 },
6813 .file_path = TMP_DIR "/dir/file",
6814 };
6815
FIXTURE_VARIANT_ADD(layout3_fs,cgroup2)6816 FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) {
6817 .mnt = {
6818 .type = "cgroup2",
6819 },
6820 .file_path = TMP_DIR "/test/cgroup.procs",
6821 };
6822
FIXTURE_VARIANT_ADD(layout3_fs,proc)6823 FIXTURE_VARIANT_ADD(layout3_fs, proc) {
6824 .mnt = {
6825 .type = "proc",
6826 },
6827 .file_path = TMP_DIR "/self/status",
6828 };
6829
FIXTURE_VARIANT_ADD(layout3_fs,sysfs)6830 FIXTURE_VARIANT_ADD(layout3_fs, sysfs) {
6831 .mnt = {
6832 .type = "sysfs",
6833 },
6834 .file_path = TMP_DIR "/kernel/notes",
6835 };
6836
FIXTURE_VARIANT_ADD(layout3_fs,hostfs)6837 FIXTURE_VARIANT_ADD(layout3_fs, hostfs) {
6838 .mnt = {
6839 .source = TMP_DIR,
6840 .flags = MS_BIND,
6841 },
6842 .file_path = TMP_DIR "/dir/file",
6843 .cwd_fs_magic = HOSTFS_SUPER_MAGIC,
6844 };
6845
dirname_alloc(const char * path)6846 static char *dirname_alloc(const char *path)
6847 {
6848 char *dup;
6849
6850 if (!path)
6851 return NULL;
6852
6853 dup = strdup(path);
6854 if (!dup)
6855 return NULL;
6856
6857 return dirname(dup);
6858 }
6859
FIXTURE_SETUP(layout3_fs)6860 FIXTURE_SETUP(layout3_fs)
6861 {
6862 struct stat statbuf;
6863 char *dir_path = dirname_alloc(variant->file_path);
6864
6865 if (!supports_filesystem(variant->mnt.type) ||
6866 !cwd_matches_fs(variant->cwd_fs_magic)) {
6867 self->skip_test = true;
6868 SKIP(return, "this filesystem is not supported (setup)");
6869 }
6870
6871 prepare_layout_opt(_metadata, &variant->mnt);
6872
6873 /* Creates directory when required. */
6874 if (stat(dir_path, &statbuf)) {
6875 set_cap(_metadata, CAP_DAC_OVERRIDE);
6876 EXPECT_EQ(0, mkdir(dir_path, 0700))
6877 {
6878 TH_LOG("Failed to create directory \"%s\": %s",
6879 dir_path, strerror(errno));
6880 }
6881 self->has_created_dir = true;
6882 clear_cap(_metadata, CAP_DAC_OVERRIDE);
6883 }
6884
6885 /* Creates file when required. */
6886 if (stat(variant->file_path, &statbuf)) {
6887 int fd;
6888
6889 set_cap(_metadata, CAP_DAC_OVERRIDE);
6890 fd = creat(variant->file_path, 0600);
6891 EXPECT_LE(0, fd)
6892 {
6893 TH_LOG("Failed to create file \"%s\": %s",
6894 variant->file_path, strerror(errno));
6895 }
6896 EXPECT_EQ(0, close(fd));
6897 self->has_created_file = true;
6898 clear_cap(_metadata, CAP_DAC_OVERRIDE);
6899 }
6900
6901 free(dir_path);
6902 }
6903
FIXTURE_TEARDOWN_PARENT(layout3_fs)6904 FIXTURE_TEARDOWN_PARENT(layout3_fs)
6905 {
6906 if (self->skip_test)
6907 SKIP(return, "this filesystem is not supported (teardown)");
6908
6909 if (self->has_created_file) {
6910 set_cap(_metadata, CAP_DAC_OVERRIDE);
6911 /*
6912 * Don't check for error because the file might already
6913 * have been removed (cf. release_inode test).
6914 */
6915 unlink(variant->file_path);
6916 clear_cap(_metadata, CAP_DAC_OVERRIDE);
6917 }
6918
6919 if (self->has_created_dir) {
6920 char *dir_path = dirname_alloc(variant->file_path);
6921
6922 set_cap(_metadata, CAP_DAC_OVERRIDE);
6923 /*
6924 * Don't check for error because the directory might already
6925 * have been removed (cf. release_inode test).
6926 */
6927 rmdir(dir_path);
6928 clear_cap(_metadata, CAP_DAC_OVERRIDE);
6929 free(dir_path);
6930 }
6931
6932 cleanup_layout(_metadata);
6933 }
6934
layer3_fs_tag_inode(struct __test_metadata * const _metadata,FIXTURE_DATA (layout3_fs)* self,const FIXTURE_VARIANT (layout3_fs)* variant,const char * const rule_path)6935 static void layer3_fs_tag_inode(struct __test_metadata *const _metadata,
6936 FIXTURE_DATA(layout3_fs) * self,
6937 const FIXTURE_VARIANT(layout3_fs) * variant,
6938 const char *const rule_path)
6939 {
6940 const struct rule layer1_allow_read_file[] = {
6941 {
6942 .path = rule_path,
6943 .access = LANDLOCK_ACCESS_FS_READ_FILE,
6944 },
6945 {},
6946 };
6947 const struct landlock_ruleset_attr layer2_deny_everything_attr = {
6948 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
6949 };
6950 const char *const dev_null_path = "/dev/null";
6951 int ruleset_fd;
6952
6953 if (self->skip_test)
6954 SKIP(return, "this filesystem is not supported (test)");
6955
6956 /* Checks without Landlock. */
6957 EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
6958 EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
6959
6960 ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
6961 layer1_allow_read_file);
6962 EXPECT_LE(0, ruleset_fd);
6963 enforce_ruleset(_metadata, ruleset_fd);
6964 EXPECT_EQ(0, close(ruleset_fd));
6965
6966 EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
6967 EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
6968
6969 /* Forbids directory reading. */
6970 ruleset_fd =
6971 landlock_create_ruleset(&layer2_deny_everything_attr,
6972 sizeof(layer2_deny_everything_attr), 0);
6973 EXPECT_LE(0, ruleset_fd);
6974 enforce_ruleset(_metadata, ruleset_fd);
6975 EXPECT_EQ(0, close(ruleset_fd));
6976
6977 /* Checks with Landlock and forbidden access. */
6978 EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
6979 EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
6980 }
6981
6982 /* Matrix of tests to check file hierarchy evaluation. */
6983
TEST_F_FORK(layout3_fs,tag_inode_dir_parent)6984 TEST_F_FORK(layout3_fs, tag_inode_dir_parent)
6985 {
6986 /* The current directory must not be the root for this test. */
6987 layer3_fs_tag_inode(_metadata, self, variant, ".");
6988 }
6989
TEST_F_FORK(layout3_fs,tag_inode_dir_mnt)6990 TEST_F_FORK(layout3_fs, tag_inode_dir_mnt)
6991 {
6992 layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR);
6993 }
6994
TEST_F_FORK(layout3_fs,tag_inode_dir_child)6995 TEST_F_FORK(layout3_fs, tag_inode_dir_child)
6996 {
6997 char *dir_path = dirname_alloc(variant->file_path);
6998
6999 layer3_fs_tag_inode(_metadata, self, variant, dir_path);
7000 free(dir_path);
7001 }
7002
TEST_F_FORK(layout3_fs,tag_inode_file)7003 TEST_F_FORK(layout3_fs, tag_inode_file)
7004 {
7005 layer3_fs_tag_inode(_metadata, self, variant, variant->file_path);
7006 }
7007
7008 /* Light version of layout1.release_inodes */
TEST_F_FORK(layout3_fs,release_inodes)7009 TEST_F_FORK(layout3_fs, release_inodes)
7010 {
7011 const struct rule layer1[] = {
7012 {
7013 .path = TMP_DIR,
7014 .access = LANDLOCK_ACCESS_FS_READ_DIR,
7015 },
7016 {},
7017 };
7018 int ruleset_fd;
7019
7020 if (self->skip_test)
7021 SKIP(return, "this filesystem is not supported (test)");
7022
7023 /* Clean up for the teardown to not fail. */
7024 if (self->has_created_file)
7025 EXPECT_EQ(0, remove_path(variant->file_path));
7026
7027 if (self->has_created_dir) {
7028 char *dir_path = dirname_alloc(variant->file_path);
7029
7030 /* Don't check for error because of cgroup specificities. */
7031 remove_path(dir_path);
7032 free(dir_path);
7033 }
7034
7035 ruleset_fd =
7036 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
7037 ASSERT_LE(0, ruleset_fd);
7038
7039 /* Unmount the filesystem while it is being used by a ruleset. */
7040 set_cap(_metadata, CAP_SYS_ADMIN);
7041 ASSERT_EQ(0, umount(TMP_DIR));
7042 clear_cap(_metadata, CAP_SYS_ADMIN);
7043
7044 /* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */
7045 set_cap(_metadata, CAP_SYS_ADMIN);
7046 ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR));
7047 clear_cap(_metadata, CAP_SYS_ADMIN);
7048
7049 enforce_ruleset(_metadata, ruleset_fd);
7050 ASSERT_EQ(0, close(ruleset_fd));
7051
7052 /* Checks that access to the new mount point is denied. */
7053 ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY));
7054 }
7055
matches_log_fs_extra(struct __test_metadata * const _metadata,int audit_fd,const char * const blockers,const char * const path,const char * const extra)7056 static int matches_log_fs_extra(struct __test_metadata *const _metadata,
7057 int audit_fd, const char *const blockers,
7058 const char *const path, const char *const extra)
7059 {
7060 static const char log_template[] = REGEX_LANDLOCK_PREFIX
7061 " blockers=fs\\.%s path=\"%s\" dev=\"[^\"]\\+\" ino=[0-9]\\+$";
7062 char *absolute_path = NULL;
7063 size_t log_match_remaining = sizeof(log_template) + strlen(blockers) +
7064 PATH_MAX * 2 +
7065 (extra ? strlen(extra) : 0) + 1;
7066 char log_match[log_match_remaining];
7067 char *log_match_cursor = log_match;
7068 size_t chunk_len;
7069
7070 chunk_len = snprintf(log_match_cursor, log_match_remaining,
7071 REGEX_LANDLOCK_PREFIX " blockers=%s path=\"",
7072 blockers);
7073 if (chunk_len < 0 || chunk_len >= log_match_remaining)
7074 return -E2BIG;
7075
7076 /*
7077 * It is assume that absolute_path does not contain control characters nor
7078 * spaces, see audit_string_contains_control().
7079 */
7080 absolute_path = realpath(path, NULL);
7081 if (!absolute_path)
7082 return -errno;
7083
7084 log_match_remaining -= chunk_len;
7085 log_match_cursor += chunk_len;
7086 log_match_cursor = regex_escape(absolute_path, log_match_cursor,
7087 log_match_remaining);
7088 free(absolute_path);
7089 if (log_match_cursor < 0)
7090 return (long long)log_match_cursor;
7091
7092 log_match_remaining -= log_match_cursor - log_match;
7093 chunk_len = snprintf(log_match_cursor, log_match_remaining,
7094 "\" dev=\"[^\"]\\+\" ino=[0-9]\\+%s$",
7095 extra ?: "");
7096 if (chunk_len < 0 || chunk_len >= log_match_remaining)
7097 return -E2BIG;
7098
7099 return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
7100 NULL);
7101 }
7102
matches_log_fs(struct __test_metadata * const _metadata,int audit_fd,const char * const blockers,const char * const path)7103 static int matches_log_fs(struct __test_metadata *const _metadata, int audit_fd,
7104 const char *const blockers, const char *const path)
7105 {
7106 return matches_log_fs_extra(_metadata, audit_fd, blockers, path, NULL);
7107 }
7108
FIXTURE(audit_layout1)7109 FIXTURE(audit_layout1)
7110 {
7111 struct audit_filter audit_filter;
7112 int audit_fd;
7113 };
7114
FIXTURE_SETUP(audit_layout1)7115 FIXTURE_SETUP(audit_layout1)
7116 {
7117 prepare_layout(_metadata);
7118
7119 create_layout1(_metadata);
7120
7121 set_cap(_metadata, CAP_AUDIT_CONTROL);
7122 self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
7123 EXPECT_LE(0, self->audit_fd);
7124 disable_caps(_metadata);
7125 }
7126
FIXTURE_TEARDOWN_PARENT(audit_layout1)7127 FIXTURE_TEARDOWN_PARENT(audit_layout1)
7128 {
7129 remove_layout1(_metadata);
7130
7131 cleanup_layout(_metadata);
7132
7133 EXPECT_EQ(0, audit_cleanup(-1, NULL));
7134 }
7135
TEST_F(audit_layout1,execute_make)7136 TEST_F(audit_layout1, execute_make)
7137 {
7138 struct audit_records records;
7139
7140 copy_file(_metadata, bin_true, file1_s1d1);
7141 test_execute(_metadata, 0, file1_s1d1);
7142 test_check_exec(_metadata, 0, file1_s1d1);
7143
7144 drop_access_rights(_metadata,
7145 &(struct landlock_ruleset_attr){
7146 .handled_access_fs =
7147 LANDLOCK_ACCESS_FS_EXECUTE,
7148 });
7149
7150 test_execute(_metadata, EACCES, file1_s1d1);
7151 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute",
7152 file1_s1d1));
7153 test_check_exec(_metadata, EACCES, file1_s1d1);
7154 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute",
7155 file1_s1d1));
7156
7157 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7158 EXPECT_EQ(0, records.access);
7159 EXPECT_EQ(0, records.domain);
7160 }
7161
7162 /*
7163 * Using a set of handled/denied access rights make it possible to check that
7164 * only the blocked ones are logged.
7165 */
7166
7167 /* clang-format off */
7168 static const __u64 access_fs_16 =
7169 LANDLOCK_ACCESS_FS_EXECUTE |
7170 LANDLOCK_ACCESS_FS_WRITE_FILE |
7171 LANDLOCK_ACCESS_FS_READ_FILE |
7172 LANDLOCK_ACCESS_FS_READ_DIR |
7173 LANDLOCK_ACCESS_FS_REMOVE_DIR |
7174 LANDLOCK_ACCESS_FS_REMOVE_FILE |
7175 LANDLOCK_ACCESS_FS_MAKE_CHAR |
7176 LANDLOCK_ACCESS_FS_MAKE_DIR |
7177 LANDLOCK_ACCESS_FS_MAKE_REG |
7178 LANDLOCK_ACCESS_FS_MAKE_SOCK |
7179 LANDLOCK_ACCESS_FS_MAKE_FIFO |
7180 LANDLOCK_ACCESS_FS_MAKE_BLOCK |
7181 LANDLOCK_ACCESS_FS_MAKE_SYM |
7182 LANDLOCK_ACCESS_FS_REFER |
7183 LANDLOCK_ACCESS_FS_TRUNCATE |
7184 LANDLOCK_ACCESS_FS_IOCTL_DEV;
7185 /* clang-format on */
7186
TEST_F(audit_layout1,execute_read)7187 TEST_F(audit_layout1, execute_read)
7188 {
7189 struct audit_records records;
7190
7191 copy_file(_metadata, bin_true, file1_s1d1);
7192 test_execute(_metadata, 0, file1_s1d1);
7193 test_check_exec(_metadata, 0, file1_s1d1);
7194
7195 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7196 .handled_access_fs = access_fs_16,
7197 });
7198
7199 /*
7200 * The only difference with the previous audit_layout1.execute_read test is
7201 * the extra ",fs\\.read_file" blocked by the executable file.
7202 */
7203 test_execute(_metadata, EACCES, file1_s1d1);
7204 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7205 "fs\\.execute,fs\\.read_file", file1_s1d1));
7206 test_check_exec(_metadata, EACCES, file1_s1d1);
7207 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7208 "fs\\.execute,fs\\.read_file", file1_s1d1));
7209
7210 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7211 EXPECT_EQ(0, records.access);
7212 EXPECT_EQ(0, records.domain);
7213 }
7214
TEST_F(audit_layout1,write_file)7215 TEST_F(audit_layout1, write_file)
7216 {
7217 struct audit_records records;
7218
7219 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7220 .handled_access_fs = access_fs_16,
7221 });
7222
7223 EXPECT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
7224 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7225 "fs\\.write_file", file1_s1d1));
7226
7227 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7228 EXPECT_EQ(0, records.access);
7229 EXPECT_EQ(1, records.domain);
7230 }
7231
TEST_F(audit_layout1,read_file)7232 TEST_F(audit_layout1, read_file)
7233 {
7234 struct audit_records records;
7235
7236 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7237 .handled_access_fs = access_fs_16,
7238 });
7239
7240 EXPECT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
7241 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_file",
7242 file1_s1d1));
7243
7244 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7245 EXPECT_EQ(0, records.access);
7246 EXPECT_EQ(1, records.domain);
7247 }
7248
TEST_F(audit_layout1,read_dir)7249 TEST_F(audit_layout1, read_dir)
7250 {
7251 struct audit_records records;
7252
7253 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7254 .handled_access_fs = access_fs_16,
7255 });
7256
7257 EXPECT_EQ(EACCES, test_open(dir_s1d1, O_DIRECTORY));
7258 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_dir",
7259 dir_s1d1));
7260
7261 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7262 EXPECT_EQ(0, records.access);
7263 EXPECT_EQ(1, records.domain);
7264 }
7265
TEST_F(audit_layout1,remove_dir)7266 TEST_F(audit_layout1, remove_dir)
7267 {
7268 struct audit_records records;
7269
7270 EXPECT_EQ(0, unlink(file1_s1d3));
7271 EXPECT_EQ(0, unlink(file2_s1d3));
7272
7273 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7274 .handled_access_fs = access_fs_16,
7275 });
7276
7277 EXPECT_EQ(-1, rmdir(dir_s1d3));
7278 EXPECT_EQ(EACCES, errno);
7279 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7280 "fs\\.remove_dir", dir_s1d2));
7281
7282 EXPECT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR));
7283 EXPECT_EQ(EACCES, errno);
7284 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7285 "fs\\.remove_dir", dir_s1d2));
7286
7287 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7288 EXPECT_EQ(0, records.access);
7289 EXPECT_EQ(0, records.domain);
7290 }
7291
TEST_F(audit_layout1,remove_file)7292 TEST_F(audit_layout1, remove_file)
7293 {
7294 struct audit_records records;
7295
7296 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7297 .handled_access_fs = access_fs_16,
7298 });
7299
7300 EXPECT_EQ(-1, unlink(file1_s1d3));
7301 EXPECT_EQ(EACCES, errno);
7302 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7303 "fs\\.remove_file", dir_s1d3));
7304
7305 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7306 EXPECT_EQ(0, records.access);
7307 EXPECT_EQ(1, records.domain);
7308 }
7309
TEST_F(audit_layout1,make_char)7310 TEST_F(audit_layout1, make_char)
7311 {
7312 struct audit_records records;
7313
7314 EXPECT_EQ(0, unlink(file1_s1d3));
7315
7316 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7317 .handled_access_fs = access_fs_16,
7318 });
7319
7320 EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0));
7321 EXPECT_EQ(EACCES, errno);
7322 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_char",
7323 dir_s1d3));
7324
7325 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7326 EXPECT_EQ(0, records.access);
7327 EXPECT_EQ(1, records.domain);
7328 }
7329
TEST_F(audit_layout1,make_dir)7330 TEST_F(audit_layout1, make_dir)
7331 {
7332 struct audit_records records;
7333
7334 EXPECT_EQ(0, unlink(file1_s1d3));
7335
7336 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7337 .handled_access_fs = access_fs_16,
7338 });
7339
7340 EXPECT_EQ(-1, mkdir(file1_s1d3, 0755));
7341 EXPECT_EQ(EACCES, errno);
7342 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_dir",
7343 dir_s1d3));
7344
7345 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7346 EXPECT_EQ(0, records.access);
7347 EXPECT_EQ(1, records.domain);
7348 }
7349
TEST_F(audit_layout1,make_reg)7350 TEST_F(audit_layout1, make_reg)
7351 {
7352 struct audit_records records;
7353
7354 EXPECT_EQ(0, unlink(file1_s1d3));
7355
7356 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7357 .handled_access_fs = access_fs_16,
7358 });
7359
7360 EXPECT_EQ(-1, mknod(file1_s1d3, S_IFREG | 0644, 0));
7361 EXPECT_EQ(EACCES, errno);
7362 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_reg",
7363 dir_s1d3));
7364
7365 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7366 EXPECT_EQ(0, records.access);
7367 EXPECT_EQ(1, records.domain);
7368 }
7369
TEST_F(audit_layout1,make_sock)7370 TEST_F(audit_layout1, make_sock)
7371 {
7372 struct audit_records records;
7373
7374 EXPECT_EQ(0, unlink(file1_s1d3));
7375
7376 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7377 .handled_access_fs = access_fs_16,
7378 });
7379
7380 EXPECT_EQ(-1, mknod(file1_s1d3, S_IFSOCK | 0644, 0));
7381 EXPECT_EQ(EACCES, errno);
7382 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_sock",
7383 dir_s1d3));
7384
7385 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7386 EXPECT_EQ(0, records.access);
7387 EXPECT_EQ(1, records.domain);
7388 }
7389
TEST_F(audit_layout1,make_fifo)7390 TEST_F(audit_layout1, make_fifo)
7391 {
7392 struct audit_records records;
7393
7394 EXPECT_EQ(0, unlink(file1_s1d3));
7395
7396 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7397 .handled_access_fs = access_fs_16,
7398 });
7399
7400 EXPECT_EQ(-1, mknod(file1_s1d3, S_IFIFO | 0644, 0));
7401 EXPECT_EQ(EACCES, errno);
7402 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_fifo",
7403 dir_s1d3));
7404
7405 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7406 EXPECT_EQ(0, records.access);
7407 EXPECT_EQ(1, records.domain);
7408 }
7409
TEST_F(audit_layout1,make_block)7410 TEST_F(audit_layout1, make_block)
7411 {
7412 struct audit_records records;
7413
7414 EXPECT_EQ(0, unlink(file1_s1d3));
7415
7416 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7417 .handled_access_fs = access_fs_16,
7418 });
7419
7420 EXPECT_EQ(-1, mknod(file1_s1d3, S_IFBLK | 0644, 0));
7421 EXPECT_EQ(EACCES, errno);
7422 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7423 "fs\\.make_block", dir_s1d3));
7424
7425 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7426 EXPECT_EQ(0, records.access);
7427 EXPECT_EQ(1, records.domain);
7428 }
7429
TEST_F(audit_layout1,make_sym)7430 TEST_F(audit_layout1, make_sym)
7431 {
7432 struct audit_records records;
7433
7434 EXPECT_EQ(0, unlink(file1_s1d3));
7435
7436 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7437 .handled_access_fs = access_fs_16,
7438 });
7439
7440 EXPECT_EQ(-1, symlink("target", file1_s1d3));
7441 EXPECT_EQ(EACCES, errno);
7442 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_sym",
7443 dir_s1d3));
7444
7445 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7446 EXPECT_EQ(0, records.access);
7447 EXPECT_EQ(1, records.domain);
7448 }
7449
TEST_F(audit_layout1,refer_handled)7450 TEST_F(audit_layout1, refer_handled)
7451 {
7452 struct audit_records records;
7453
7454 EXPECT_EQ(0, unlink(file1_s1d3));
7455
7456 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7457 .handled_access_fs =
7458 LANDLOCK_ACCESS_FS_REFER,
7459 });
7460
7461 EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
7462 EXPECT_EQ(EXDEV, errno);
7463 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
7464 dir_s1d1));
7465 EXPECT_EQ(0,
7466 matches_log_domain_allocated(self->audit_fd, getpid(), NULL));
7467 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
7468 dir_s1d3));
7469
7470 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7471 EXPECT_EQ(0, records.access);
7472 EXPECT_EQ(0, records.domain);
7473 }
7474
TEST_F(audit_layout1,refer_make)7475 TEST_F(audit_layout1, refer_make)
7476 {
7477 struct audit_records records;
7478
7479 EXPECT_EQ(0, unlink(file1_s1d3));
7480
7481 drop_access_rights(_metadata,
7482 &(struct landlock_ruleset_attr){
7483 .handled_access_fs =
7484 LANDLOCK_ACCESS_FS_MAKE_REG |
7485 LANDLOCK_ACCESS_FS_REFER,
7486 });
7487
7488 EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
7489 EXPECT_EQ(EACCES, errno);
7490 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
7491 dir_s1d1));
7492 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7493 "fs\\.make_reg,fs\\.refer", dir_s1d3));
7494
7495 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7496 EXPECT_EQ(0, records.access);
7497 EXPECT_EQ(0, records.domain);
7498 }
7499
TEST_F(audit_layout1,refer_rename)7500 TEST_F(audit_layout1, refer_rename)
7501 {
7502 struct audit_records records;
7503
7504 EXPECT_EQ(0, unlink(file1_s1d3));
7505
7506 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7507 .handled_access_fs = access_fs_16,
7508 });
7509
7510 EXPECT_EQ(EACCES, test_rename(file1_s1d2, file1_s2d3));
7511 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7512 "fs\\.remove_file,fs\\.refer", dir_s1d2));
7513 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7514 "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
7515 dir_s2d3));
7516
7517 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7518 EXPECT_EQ(0, records.access);
7519 EXPECT_EQ(0, records.domain);
7520 }
7521
TEST_F(audit_layout1,refer_exchange)7522 TEST_F(audit_layout1, refer_exchange)
7523 {
7524 struct audit_records records;
7525
7526 EXPECT_EQ(0, unlink(file1_s1d3));
7527
7528 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7529 .handled_access_fs = access_fs_16,
7530 });
7531
7532 /*
7533 * The only difference with the previous audit_layout1.refer_rename test is
7534 * the extra ",fs\\.make_reg" blocked by the source directory.
7535 */
7536 EXPECT_EQ(EACCES, test_exchange(file1_s1d2, file1_s2d3));
7537 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7538 "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
7539 dir_s1d2));
7540 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7541 "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
7542 dir_s2d3));
7543
7544 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7545 EXPECT_EQ(0, records.access);
7546 EXPECT_EQ(0, records.domain);
7547 }
7548
7549 /*
7550 * This test checks that the audit record is correctly generated when the
7551 * operation is only partially denied. This is the case for rename(2) when the
7552 * source file is allowed to be referenced but the destination directory is not.
7553 *
7554 * This is also a regression test for commit d617f0d72d80 ("landlock: Optimize
7555 * file path walks and prepare for audit support") and commit 058518c20920
7556 * ("landlock: Align partial refer access checks with final ones").
7557 */
TEST_F(audit_layout1,refer_rename_half)7558 TEST_F(audit_layout1, refer_rename_half)
7559 {
7560 struct audit_records records;
7561 const struct rule layer1[] = {
7562 {
7563 .path = dir_s2d2,
7564 .access = LANDLOCK_ACCESS_FS_REFER,
7565 },
7566 {},
7567 };
7568 int ruleset_fd =
7569 create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
7570
7571 ASSERT_LE(0, ruleset_fd);
7572 enforce_ruleset(_metadata, ruleset_fd);
7573 ASSERT_EQ(0, close(ruleset_fd));
7574
7575 ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
7576 ASSERT_EQ(EXDEV, errno);
7577
7578 /* Only half of the request is denied. */
7579 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
7580 dir_s1d1));
7581
7582 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7583 EXPECT_EQ(0, records.access);
7584 EXPECT_EQ(1, records.domain);
7585 }
7586
TEST_F(audit_layout1,truncate)7587 TEST_F(audit_layout1, truncate)
7588 {
7589 struct audit_records records;
7590
7591 drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
7592 .handled_access_fs = access_fs_16,
7593 });
7594
7595 EXPECT_EQ(-1, truncate(file1_s1d3, 0));
7596 EXPECT_EQ(EACCES, errno);
7597 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.truncate",
7598 file1_s1d3));
7599
7600 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7601 EXPECT_EQ(0, records.access);
7602 EXPECT_EQ(1, records.domain);
7603 }
7604
TEST_F(audit_layout1,ioctl_dev)7605 TEST_F(audit_layout1, ioctl_dev)
7606 {
7607 struct audit_records records;
7608 int fd;
7609
7610 drop_access_rights(_metadata,
7611 &(struct landlock_ruleset_attr){
7612 .handled_access_fs =
7613 access_fs_16 &
7614 ~LANDLOCK_ACCESS_FS_READ_FILE,
7615 });
7616
7617 fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
7618 ASSERT_LE(0, fd);
7619 EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
7620 EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd,
7621 "fs\\.ioctl_dev", "/dev/null",
7622 " ioctlcmd=0x541b"));
7623
7624 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7625 EXPECT_EQ(0, records.access);
7626 EXPECT_EQ(1, records.domain);
7627 }
7628
TEST_F(audit_layout1,mount)7629 TEST_F(audit_layout1, mount)
7630 {
7631 struct audit_records records;
7632
7633 drop_access_rights(_metadata,
7634 &(struct landlock_ruleset_attr){
7635 .handled_access_fs =
7636 LANDLOCK_ACCESS_FS_EXECUTE,
7637 });
7638
7639 set_cap(_metadata, CAP_SYS_ADMIN);
7640 EXPECT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL));
7641 EXPECT_EQ(EPERM, errno);
7642 clear_cap(_metadata, CAP_SYS_ADMIN);
7643 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
7644 "fs\\.change_topology", dir_s3d2));
7645 EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
7646 EXPECT_EQ(0, records.access);
7647 EXPECT_EQ(1, records.domain);
7648 }
7649
7650 TEST_HARNESS_MAIN
7651