1 /* SPDX-License-Identifier: GPL-2.0 */ 2 /* 3 * Landlock trace test helpers 4 * 5 * Copyright © 2026 Cloudflare, Inc. 6 */ 7 8 #define _GNU_SOURCE 9 #include <errno.h> 10 #include <fcntl.h> 11 #include <regex.h> 12 #include <stdbool.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sys/mount.h> 17 #include <sys/stat.h> 18 #include <unistd.h> 19 20 #include "kselftest_harness.h" 21 22 #define TRACEFS_ROOT "/sys/kernel/tracing" 23 #define TRACEFS_LANDLOCK_DIR TRACEFS_ROOT "/events/landlock" 24 #define TRACEFS_CREATE_RULESET_ENABLE \ 25 TRACEFS_LANDLOCK_DIR "/landlock_create_ruleset/enable" 26 #define TRACEFS_CREATE_DOMAIN_ENABLE \ 27 TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable" 28 #define TRACEFS_ENFORCE_DOMAIN_ENABLE \ 29 TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable" 30 #define TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE \ 31 TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable" 32 #define TRACEFS_ADD_RULE_NET_PORT_ENABLE \ 33 TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable" 34 #define TRACEFS_ADD_RULE_NAMESPACE_ENABLE \ 35 TRACEFS_LANDLOCK_DIR "/landlock_add_rule_namespace/enable" 36 #define TRACEFS_ADD_RULE_CAPABILITY_ENABLE \ 37 TRACEFS_LANDLOCK_DIR "/landlock_add_rule_capability/enable" 38 #define TRACEFS_CHECK_RULE_FS_ENABLE \ 39 TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable" 40 #define TRACEFS_CHECK_RULE_NET_ENABLE \ 41 TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable" 42 #define TRACEFS_DENY_ACCESS_FS_ENABLE \ 43 TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable" 44 #define TRACEFS_DENY_ACCESS_NET_ENABLE \ 45 TRACEFS_LANDLOCK_DIR "/landlock_deny_access_net/enable" 46 #define TRACEFS_DENY_PERMISSION_NAMESPACE_ENABLE \ 47 TRACEFS_LANDLOCK_DIR "/landlock_deny_permission_namespace/enable" 48 #define TRACEFS_DENY_PERMISSION_CAPABILITY_ENABLE \ 49 TRACEFS_LANDLOCK_DIR "/landlock_deny_permission_capability/enable" 50 #define TRACEFS_DENY_PTRACE_ENABLE \ 51 TRACEFS_LANDLOCK_DIR "/landlock_deny_ptrace/enable" 52 #define TRACEFS_DENY_SCOPE_SIGNAL_ENABLE \ 53 TRACEFS_LANDLOCK_DIR "/landlock_deny_scope_signal/enable" 54 #define TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE \ 55 TRACEFS_LANDLOCK_DIR \ 56 "/landlock_deny_scope_abstract_unix_socket/enable" 57 #define TRACEFS_FREE_DOMAIN_ENABLE \ 58 TRACEFS_LANDLOCK_DIR "/landlock_free_domain/enable" 59 #define TRACEFS_FREE_RULESET_ENABLE \ 60 TRACEFS_LANDLOCK_DIR "/landlock_free_ruleset/enable" 61 #define TRACEFS_TRACE TRACEFS_ROOT "/trace" 62 #define TRACEFS_SET_EVENT_PID TRACEFS_ROOT "/set_event_pid" 63 #define TRACEFS_OPTIONS_EVENT_FORK TRACEFS_ROOT "/options/event-fork" 64 65 #define TRACE_BUFFER_SIZE (64 * 1024) 66 67 /* 68 * Trace line prefix: matches the ftrace "trace" file format. Format: " 69 * <task>-<pid> [<cpu>] <flags> <timestamp>: " 70 * 71 * The task parameter must be a string literal truncated to 15 chars 72 * (TASK_COMM_LEN - 1), matching what the kernel stores in task->comm. The 73 * pattern accepts either the expected task name or "<...>" because the ftrace 74 * comm cache may evict short-lived processes (e.g., forked children that exit 75 * before the trace buffer is read). 76 * 77 * No unescaped '.' in any REGEX macro; literal dots use '\\.'. 78 */ 79 #define TRACE_PREFIX(task) \ 80 "^ *\\(<\\.\\.\\.>" \ 81 "\\|" task "\\)" \ 82 "-[0-9]\\+ *\\[[0-9]\\+\\] [^ ]\\+ \\+[0-9]\\+\\.[0-9]\\+: " 83 84 /* 85 * Task name for events emitted by kworker threads (e.g., free_domain fires from 86 * a work queue, not from the test process). 87 */ 88 #define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+" 89 90 #define REGEX_ADD_RULE_PATH_BENEATH(task) \ 91 TRACE_PREFIX(task) \ 92 "landlock_add_rule_path_beneath: " \ 93 "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ 94 "access_rights=[a-z_|]* " \ 95 "dev=[0-9]\\+:[0-9]\\+ " \ 96 "ino=[0-9]\\+ " \ 97 "path=[^ ]\\+$" 98 99 #define REGEX_ADD_RULE_NET_PORT(task) \ 100 TRACE_PREFIX(task) \ 101 "landlock_add_rule_net_port: " \ 102 "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ 103 "access_rights=[a-z_|]* " \ 104 "port=[0-9]\\+$" 105 106 #define REGEX_ADD_RULE_NAMESPACE_VERSION(task, version) \ 107 TRACE_PREFIX(task) \ 108 "landlock_add_rule_namespace: " \ 109 "ruleset=[0-9a-f]\\+\\." version " " \ 110 "permissions=[a-z._|]* " \ 111 "allowed_namespace_types=0x[0-9a-f]\\+ " \ 112 "quiet_namespace_types=0x[0-9a-f]\\+$" 113 114 #define REGEX_ADD_RULE_NAMESPACE(task) \ 115 REGEX_ADD_RULE_NAMESPACE_VERSION(task, "[0-9]\\+") 116 117 #define REGEX_ADD_RULE_CAPABILITY_VERSION(task, version) \ 118 TRACE_PREFIX(task) \ 119 "landlock_add_rule_capability: " \ 120 "ruleset=[0-9a-f]\\+\\." version " " \ 121 "permissions=[a-z._|]* " \ 122 "allowed_capabilities=0x[0-9a-f]\\+ " \ 123 "quiet_capabilities=0x[0-9a-f]\\+$" 124 125 #define REGEX_ADD_RULE_CAPABILITY(task) \ 126 REGEX_ADD_RULE_CAPABILITY_VERSION(task, "[0-9]\\+") 127 128 #define REGEX_CREATE_RULESET(task) \ 129 TRACE_PREFIX(task) \ 130 "landlock_create_ruleset: " \ 131 "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ 132 "handled_fs=[a-z_|]* " \ 133 "handled_net=[a-z_|]* " \ 134 "scoped=[a-z_|]* " \ 135 "handled_permissions=[a-z._|]*$" 136 137 #define REGEX_CREATE_DOMAIN(task) \ 138 TRACE_PREFIX(task) \ 139 "landlock_create_domain: " \ 140 "domain=[0-9a-f]\\+ " \ 141 "parent=[0-9a-f]\\+ " \ 142 "ruleset=[0-9a-f]\\+\\.[0-9]\\+$" 143 144 #define REGEX_CHECK_RULE_FS(task) \ 145 TRACE_PREFIX(task) \ 146 "landlock_check_rule_inode: " \ 147 "domain=[0-9a-f]\\+ " \ 148 "access_request=[a-z_|]* " \ 149 "dev=[0-9]\\+:[0-9]\\+ " \ 150 "ino=[0-9]\\+ " \ 151 "grants={[a-z_|,]*}$" 152 153 #define REGEX_CHECK_RULE_NET(task) \ 154 TRACE_PREFIX(task) \ 155 "landlock_check_rule_net_port: " \ 156 "domain=[0-9a-f]\\+ " \ 157 "access_request=[a-z_|]* " \ 158 "port=[0-9]\\+ " \ 159 "grants={[a-z_|,]*}$" 160 161 #define REGEX_DENY_ACCESS_FS(task) \ 162 TRACE_PREFIX(task) \ 163 "landlock_deny_access_fs: " \ 164 "domain=[0-9a-f]\\+ " \ 165 "same_exec=[01] " \ 166 "logged=[01] " \ 167 "blockers=[a-z_|]* " \ 168 "dev=[0-9]\\+:[0-9]\\+ " \ 169 "ino=[0-9]\\+ " \ 170 "path=[^ ]*$" 171 172 #define REGEX_DENY_ACCESS_NET(task) \ 173 TRACE_PREFIX(task) \ 174 "landlock_deny_access_net: " \ 175 "domain=[0-9a-f]\\+ " \ 176 "same_exec=[01] " \ 177 "logged=[01] " \ 178 "blockers=[a-z_|]* " \ 179 "port=-\\?[0-9]\\+$" 180 181 #define REGEX_DENY_PERMISSION_NAMESPACE(task) \ 182 TRACE_PREFIX(task) \ 183 "landlock_deny_permission_namespace: " \ 184 "domain=[0-9a-f]\\+ " \ 185 "same_exec=[01] " \ 186 "logged=[01] " \ 187 "blockers=[a-z_|]* " \ 188 "namespace_type=0x[0-9a-f]\\+ " \ 189 "namespace_id=[0-9]\\+$" 190 191 #define REGEX_DENY_PERMISSION_CAPABILITY(task) \ 192 TRACE_PREFIX(task) \ 193 "landlock_deny_permission_capability: " \ 194 "domain=[0-9a-f]\\+ " \ 195 "same_exec=[01] " \ 196 "logged=[01] " \ 197 "blockers=[a-z_|]* " \ 198 "capability=[0-9]\\+$" 199 200 #define REGEX_DENY_PTRACE(task) \ 201 TRACE_PREFIX(task) \ 202 "landlock_deny_ptrace: " \ 203 "domain=[0-9a-f]\\+ " \ 204 "same_exec=[01] " \ 205 "logged=[01] " \ 206 "tracee_domain=[0-9a-f]\\+ " \ 207 "tracee_pid=[0-9]\\+ " \ 208 "tracee_comm=[^ ]*$" 209 210 #define REGEX_DENY_SCOPE_SIGNAL(task) \ 211 TRACE_PREFIX(task) \ 212 "landlock_deny_scope_signal: " \ 213 "domain=[0-9a-f]\\+ " \ 214 "same_exec=[01] " \ 215 "logged=[01] " \ 216 "target_domain=[0-9a-f]\\+ " \ 217 "target_pid=[0-9]\\+ " \ 218 "target_comm=[^ ]*$" 219 220 #define REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(task) \ 221 TRACE_PREFIX(task) \ 222 "landlock_deny_scope_abstract_unix_socket: " \ 223 "domain=[0-9a-f]\\+ " \ 224 "same_exec=[01] " \ 225 "logged=[01] " \ 226 "peer_domain=[0-9a-f]\\+ " \ 227 "peer_pid=[0-9]\\+ " \ 228 "sun_path=[^ ]*$" 229 230 #define REGEX_FREE_DOMAIN(task) \ 231 TRACE_PREFIX(task) \ 232 "landlock_free_domain: " \ 233 "domain=[0-9a-f]\\+ " \ 234 "denials=[0-9]\\+$" 235 236 #define REGEX_FREE_RULESET(task) \ 237 TRACE_PREFIX(task) \ 238 "landlock_free_ruleset: " \ 239 "ruleset=[0-9a-f]\\+\\.[0-9]\\+$" 240 241 static int __maybe_unused tracefs_write(const char *path, const char *value) 242 { 243 int fd; 244 ssize_t ret; 245 size_t len = strlen(value); 246 247 fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC); 248 if (fd < 0) 249 return -errno; 250 251 ret = write(fd, value, len); 252 close(fd); 253 if (ret < 0) 254 return -errno; 255 if ((size_t)ret != len) 256 return -EIO; 257 258 return 0; 259 } 260 261 static int __maybe_unused tracefs_write_int(const char *path, int value) 262 { 263 char buf[32]; 264 265 snprintf(buf, sizeof(buf), "%d", value); 266 return tracefs_write(path, buf); 267 } 268 269 static int __maybe_unused tracefs_setup(void) 270 { 271 struct stat st; 272 273 /* Mount tracefs if not already mounted. */ 274 if (stat(TRACEFS_ROOT, &st) != 0) { 275 int ret = mount("tracefs", TRACEFS_ROOT, "tracefs", 0, NULL); 276 277 if (ret) 278 return -errno; 279 } 280 281 /* Verify landlock events are available. */ 282 if (stat(TRACEFS_LANDLOCK_DIR, &st) != 0) 283 return -ENOENT; 284 285 return 0; 286 } 287 288 /* 289 * Set up PID-based event filtering so only events from the current process and 290 * its children are recorded. This is analogous to audit's AUDIT_EXE filter: it 291 * prevents events from unrelated processes from polluting the trace buffer. 292 */ 293 static int __maybe_unused tracefs_set_pid_filter(pid_t pid) 294 { 295 int ret; 296 297 /* Enable event-fork so children inherit the PID filter. */ 298 ret = tracefs_write(TRACEFS_OPTIONS_EVENT_FORK, "1"); 299 if (ret) 300 return ret; 301 302 return tracefs_write_int(TRACEFS_SET_EVENT_PID, pid); 303 } 304 305 /* Clear the PID filter to stop filtering by PID. */ 306 static int __maybe_unused tracefs_clear_pid_filter(void) 307 { 308 return tracefs_write(TRACEFS_SET_EVENT_PID, ""); 309 } 310 311 static int __maybe_unused tracefs_enable_event(const char *enable_path, 312 bool enable) 313 { 314 return tracefs_write(enable_path, enable ? "1" : "0"); 315 } 316 317 static int __maybe_unused tracefs_clear(void) 318 { 319 return tracefs_write(TRACEFS_TRACE, ""); 320 } 321 322 /* 323 * Reads the trace buffer content into a newly allocated buffer. The caller is 324 * responsible for freeing the returned buffer. Returns NULL on error. 325 */ 326 static char __maybe_unused *tracefs_read_trace(void) 327 { 328 char *buf; 329 int fd; 330 ssize_t total = 0, ret; 331 332 buf = malloc(TRACE_BUFFER_SIZE); 333 if (!buf) 334 return NULL; 335 336 fd = open(TRACEFS_TRACE, O_RDONLY | O_CLOEXEC); 337 if (fd < 0) { 338 free(buf); 339 return NULL; 340 } 341 342 while (total < TRACE_BUFFER_SIZE - 1) { 343 ret = read(fd, buf + total, TRACE_BUFFER_SIZE - 1 - total); 344 if (ret <= 0) 345 break; 346 total += ret; 347 } 348 close(fd); 349 buf[total] = '\0'; 350 return buf; 351 } 352 353 /* Counts the number of lines in @buf matching the basic regex @pattern. */ 354 static int __maybe_unused tracefs_count_matches(const char *buf, 355 const char *pattern) 356 { 357 regex_t regex; 358 int count = 0; 359 const char *line, *end; 360 361 if (regcomp(®ex, pattern, 0) != 0) 362 return -EINVAL; 363 364 line = buf; 365 while (*line) { 366 end = strchr(line, '\n'); 367 if (!end) 368 end = line + strlen(line); 369 370 /* Create a temporary NUL-terminated line. */ 371 size_t len = end - line; 372 char *tmp = malloc(len + 1); 373 374 if (tmp) { 375 memcpy(tmp, line, len); 376 tmp[len] = '\0'; 377 if (regexec(®ex, tmp, 0, NULL, 0) == 0) 378 count++; 379 free(tmp); 380 } 381 382 if (*end == '\n') 383 line = end + 1; 384 else 385 break; 386 } 387 388 regfree(®ex); 389 return count; 390 } 391 392 /* 393 * Extracts the value of a named field from a trace line in @buf. Searches for 394 * the first line matching @line_pattern, then extracts the value after 395 * "@field_name=" into @out. Stops at space or newline. 396 * 397 * Returns 0 on success, -ENOENT if no match. 398 */ 399 static int __maybe_unused tracefs_extract_field(const char *buf, 400 const char *line_pattern, 401 const char *field_name, 402 char *out, size_t out_size) 403 { 404 regex_t regex; 405 const char *line, *end; 406 407 if (regcomp(®ex, line_pattern, 0) != 0) 408 return -EINVAL; 409 410 line = buf; 411 while (*line) { 412 end = strchr(line, '\n'); 413 if (!end) 414 end = line + strlen(line); 415 416 size_t len = end - line; 417 char *tmp = malloc(len + 1); 418 419 if (tmp) { 420 const char *field, *val_start; 421 size_t field_len, val_len; 422 423 memcpy(tmp, line, len); 424 tmp[len] = '\0'; 425 426 if (regexec(®ex, tmp, 0, NULL, 0) != 0) { 427 free(tmp); 428 goto next; 429 } 430 431 /* 432 * Find "field_name=" in the line, ensuring a word 433 * boundary before the field name to avoid substring 434 * matches (e.g., "port" in "sport"). 435 */ 436 field_len = strlen(field_name); 437 field = tmp; 438 while ((field = strstr(field, field_name))) { 439 if (field[field_len] == '=' && 440 (field == tmp || field[-1] == ' ')) 441 break; 442 field++; 443 } 444 if (!field) { 445 free(tmp); 446 regfree(®ex); 447 return -ENOENT; 448 } 449 450 val_start = field + field_len + 1; 451 val_len = 0; 452 while (val_start[val_len] && 453 val_start[val_len] != ' ' && 454 val_start[val_len] != '\n') 455 val_len++; 456 457 if (val_len >= out_size) 458 val_len = out_size - 1; 459 memcpy(out, val_start, val_len); 460 out[val_len] = '\0'; 461 462 free(tmp); 463 regfree(®ex); 464 return 0; 465 } 466 next: 467 if (*end == '\n') 468 line = end + 1; 469 else 470 break; 471 } 472 473 regfree(®ex); 474 return -ENOENT; 475 } 476 477 /* 478 * Common fixture setup for trace tests. Mounts tracefs if needed and sets a 479 * PID filter. The caller must create a mount namespace first 480 * (unshare(CLONE_NEWNS) + mount(MS_REC | MS_PRIVATE)) to isolate the tracefs 481 * mount; the trace buffer, per-event enable flags, and PID filter are global 482 * kernel state, scoped to the test by the PID filter. 483 * 484 * Returns 0 on success, -errno on failure (caller should SKIP). 485 */ 486 static int __maybe_unused tracefs_fixture_setup(void) 487 { 488 int ret; 489 490 ret = tracefs_setup(); 491 if (ret) 492 return ret; 493 494 return tracefs_set_pid_filter(getpid()); 495 } 496 497 static void __maybe_unused tracefs_fixture_teardown(void) 498 { 499 tracefs_clear_pid_filter(); 500 } 501 502 /* 503 * Temporarily raises CAP_SYS_ADMIN effective capability, calls @func, then 504 * drops the capability. Returns the value from @func, or -EPERM if the 505 * capability manipulation fails. 506 */ 507 static int __maybe_unused tracefs_priv_call(int (*func)(void)) 508 { 509 const cap_value_t admin = CAP_SYS_ADMIN; 510 cap_t cap_p; 511 int ret; 512 513 cap_p = cap_get_proc(); 514 if (!cap_p) 515 return -EPERM; 516 517 if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) || 518 cap_set_proc(cap_p)) { 519 cap_free(cap_p); 520 return -EPERM; 521 } 522 523 ret = func(); 524 525 cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR); 526 cap_set_proc(cap_p); 527 cap_free(cap_p); 528 return ret; 529 } 530 531 /* Read the trace buffer with elevated privileges. Returns NULL on failure. */ 532 static char __maybe_unused *tracefs_read_buf(void) 533 { 534 /* Cannot use tracefs_priv_call() because the return type is char *. */ 535 cap_t cap_p; 536 char *buf; 537 const cap_value_t admin = CAP_SYS_ADMIN; 538 539 cap_p = cap_get_proc(); 540 if (!cap_p) 541 return NULL; 542 543 if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) || 544 cap_set_proc(cap_p)) { 545 cap_free(cap_p); 546 return NULL; 547 } 548 549 buf = tracefs_read_trace(); 550 551 cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR); 552 cap_set_proc(cap_p); 553 cap_free(cap_p); 554 return buf; 555 } 556 557 /* Clear the trace buffer with elevated privileges. Returns 0 on success. */ 558 static int __maybe_unused tracefs_clear_buf(void) 559 { 560 return tracefs_priv_call(tracefs_clear); 561 } 562 563 /* 564 * Forks a child that creates a Landlock sandbox and performs an FS access. The 565 * parent waits for the child, then reads the trace buffer. 566 * 567 * Requires common.h and wrappers.h to be included before trace.h. 568 */ 569 static void __maybe_unused sandbox_child_fs_access( 570 struct __test_metadata *const _metadata, const char *rule_path, 571 __u64 handled_access, __u64 allowed_access, const char *access_path) 572 { 573 pid_t pid; 574 int status; 575 576 pid = fork(); 577 ASSERT_LE(0, pid); 578 579 if (pid == 0) { 580 struct landlock_ruleset_attr ruleset_attr = { 581 .handled_access_fs = handled_access, 582 }; 583 struct landlock_path_beneath_attr path_beneath = { 584 .allowed_access = allowed_access, 585 }; 586 int ruleset_fd, fd; 587 588 ruleset_fd = landlock_create_ruleset(&ruleset_attr, 589 sizeof(ruleset_attr), 0); 590 if (ruleset_fd < 0) 591 _exit(1); 592 593 path_beneath.parent_fd = 594 open(rule_path, O_PATH | O_DIRECTORY | O_CLOEXEC); 595 if (path_beneath.parent_fd < 0) { 596 close(ruleset_fd); 597 _exit(1); 598 } 599 600 if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 601 &path_beneath, 0)) { 602 close(path_beneath.parent_fd); 603 close(ruleset_fd); 604 _exit(1); 605 } 606 close(path_beneath.parent_fd); 607 608 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 609 if (landlock_restrict_self(ruleset_fd, 0)) { 610 close(ruleset_fd); 611 _exit(1); 612 } 613 close(ruleset_fd); 614 615 fd = open(access_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 616 if (fd >= 0) 617 close(fd); 618 619 _exit(0); 620 } 621 622 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 623 ASSERT_TRUE(WIFEXITED(status)); 624 EXPECT_EQ(0, WEXITSTATUS(status)); 625 } 626 627 /* 628 * Forks a child that creates a Landlock sandbox allowing execute+read_dir for 629 * /usr and execute-only for ".", then execs ./true. The true binary opens "." 630 * on startup, triggering a read_dir denial with same_exec=0. The parent waits 631 * for the child to exit. 632 */ 633 static void __maybe_unused sandbox_child_exec_true( 634 struct __test_metadata *const _metadata, __u32 restrict_flags) 635 { 636 pid_t pid; 637 int status; 638 639 pid = fork(); 640 ASSERT_LE(0, pid); 641 642 if (pid == 0) { 643 struct landlock_ruleset_attr attr = { 644 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR | 645 LANDLOCK_ACCESS_FS_EXECUTE, 646 }; 647 struct landlock_path_beneath_attr path_beneath = { 648 .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE | 649 LANDLOCK_ACCESS_FS_READ_DIR, 650 }; 651 int ruleset_fd; 652 653 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 654 if (ruleset_fd < 0) 655 _exit(1); 656 657 path_beneath.parent_fd = 658 open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 659 if (path_beneath.parent_fd >= 0) { 660 landlock_add_rule(ruleset_fd, 661 LANDLOCK_RULE_PATH_BENEATH, 662 &path_beneath, 0); 663 close(path_beneath.parent_fd); 664 } 665 666 path_beneath.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE; 667 path_beneath.parent_fd = 668 open(".", O_PATH | O_DIRECTORY | O_CLOEXEC); 669 if (path_beneath.parent_fd >= 0) { 670 landlock_add_rule(ruleset_fd, 671 LANDLOCK_RULE_PATH_BENEATH, 672 &path_beneath, 0); 673 close(path_beneath.parent_fd); 674 } 675 676 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 677 if (landlock_restrict_self(ruleset_fd, restrict_flags)) 678 _exit(1); 679 close(ruleset_fd); 680 681 execl("./true", "./true", NULL); 682 _exit(1); 683 } 684 685 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 686 ASSERT_TRUE(WIFEXITED(status)); 687 EXPECT_EQ(0, WEXITSTATUS(status)); 688 } 689