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