1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Landlock tests - Tracepoints 4 * 5 * Copyright © 2026 Cloudflare, Inc. 6 */ 7 8 #define _GNU_SOURCE 9 #include <errno.h> 10 #include <fcntl.h> 11 #include <linux/landlock.h> 12 #include <pthread.h> 13 #include <sched.h> 14 #include <stdio.h> 15 #include <string.h> 16 #include <sys/mount.h> 17 #include <sys/stat.h> 18 #include <sys/types.h> 19 #include <sys/wait.h> 20 #include <unistd.h> 21 22 #include "common.h" 23 #include "trace.h" 24 25 #define TRACE_TASK "trace_test" 26 27 /* clang-format off */ 28 FIXTURE(trace) { 29 /* clang-format on */ 30 int tracefs_ok; 31 }; 32 33 FIXTURE_SETUP(trace) 34 { 35 int ret; 36 37 set_cap(_metadata, CAP_SYS_ADMIN); 38 ASSERT_EQ(0, unshare(CLONE_NEWNS)); 39 ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); 40 41 ret = tracefs_fixture_setup(); 42 if (ret) { 43 clear_cap(_metadata, CAP_SYS_ADMIN); 44 self->tracefs_ok = 0; 45 SKIP(return, "tracefs not available"); 46 } 47 self->tracefs_ok = 1; 48 49 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); 50 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); 51 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); 52 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, 53 true)); 54 ASSERT_EQ(0, 55 tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true)); 56 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); 57 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true)); 58 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); 59 ASSERT_EQ(0, 60 tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, true)); 61 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, true)); 62 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, true)); 63 ASSERT_EQ(0, tracefs_clear()); 64 clear_cap(_metadata, CAP_SYS_ADMIN); 65 } 66 67 FIXTURE_TEARDOWN(trace) 68 { 69 if (!self->tracefs_ok) 70 return; 71 72 /* Disables landlock events and clears PID filter. */ 73 set_cap(_metadata, CAP_SYS_ADMIN); 74 tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); 75 tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); 76 tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); 77 tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); 78 tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false); 79 tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); 80 tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false); 81 tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); 82 tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false); 83 tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false); 84 tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false); 85 tracefs_clear_pid_filter(); 86 clear_cap(_metadata, CAP_SYS_ADMIN); 87 88 /* 89 * The mount namespace is cleaned up automatically when the test process 90 * (harness child) exits. 91 */ 92 } 93 94 /* 95 * Verifies that no trace events are emitted when the tracepoints are disabled. 96 */ 97 TEST_F(trace, no_trace_when_disabled) 98 { 99 char *buf; 100 101 /* Disable all landlock events. */ 102 set_cap(_metadata, CAP_SYS_ADMIN); 103 ASSERT_EQ(0, 104 tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false)); 105 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false)); 106 ASSERT_EQ(0, 107 tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false)); 108 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, 109 false)); 110 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, 111 false)); 112 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false)); 113 ASSERT_EQ(0, 114 tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false)); 115 ASSERT_EQ(0, 116 tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false)); 117 ASSERT_EQ(0, 118 tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false)); 119 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false)); 120 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, 121 false)); 122 ASSERT_EQ(0, tracefs_enable_event( 123 TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE, 124 false)); 125 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false)); 126 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false)); 127 ASSERT_EQ(0, tracefs_clear()); 128 clear_cap(_metadata, CAP_SYS_ADMIN); 129 130 /* 131 * Trigger both allowed and denied accesses to verify neither check_rule 132 * nor check_access events fire when disabled. 133 */ 134 sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 135 LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 136 137 /* Read trace buffer and verify no landlock events at all. */ 138 buf = tracefs_read_buf(); 139 ASSERT_NE(NULL, buf); 140 141 EXPECT_EQ(0, tracefs_count_matches(buf, "landlock_")) 142 { 143 TH_LOG("Expected 0 landlock events when disabled\n%s", buf); 144 } 145 146 free(buf); 147 } 148 149 /* 150 * Verifies that landlock_create_ruleset emits a trace event with the correct 151 * handled access masks. 152 */ 153 TEST_F(trace, create_ruleset) 154 { 155 struct landlock_ruleset_attr ruleset_attr = { 156 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, 157 .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, 158 }; 159 int ruleset_fd; 160 char *buf, *dot; 161 char field[64]; 162 163 ruleset_fd = 164 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 165 ASSERT_LE(0, ruleset_fd); 166 ASSERT_EQ(0, close(ruleset_fd)); 167 168 buf = tracefs_read_buf(); 169 ASSERT_NE(NULL, buf); 170 171 EXPECT_EQ(1, 172 tracefs_count_matches(buf, REGEX_CREATE_RULESET(TRACE_TASK))) 173 { 174 TH_LOG("Expected 1 create_ruleset event\n%s", buf); 175 } 176 177 /* Verify handled_fs matches what we requested. */ 178 EXPECT_EQ(0, 179 tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 180 "handled_fs", field, sizeof(field))); 181 EXPECT_STREQ("read_file", field); 182 183 /* Verify handled_net matches. */ 184 EXPECT_EQ(0, 185 tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 186 "handled_net", field, sizeof(field))); 187 EXPECT_STREQ("bind_tcp", field); 188 189 /* Verify version is 0 at creation (no rules added yet). */ 190 EXPECT_EQ(0, 191 tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 192 "ruleset", field, sizeof(field))); 193 /* Format is <hex>.<dec>; version is after the dot. */ 194 dot = strchr(field, '.'); 195 ASSERT_NE(0, !!dot); 196 EXPECT_STREQ("0", dot + 1); 197 198 free(buf); 199 } 200 201 /* 202 * Verifies that the ruleset version increments with each add_rule call and that 203 * create_domain records the correct version. 204 */ 205 TEST_F(trace, ruleset_version) 206 { 207 pid_t pid; 208 int status; 209 char *buf; 210 const char *dot; 211 char field[64]; 212 213 ASSERT_EQ(0, tracefs_clear_buf()); 214 215 pid = fork(); 216 ASSERT_LE(0, pid); 217 218 if (pid == 0) { 219 struct landlock_ruleset_attr ruleset_attr = { 220 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 221 }; 222 struct landlock_path_beneath_attr path_beneath = { 223 .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, 224 }; 225 int ruleset_fd; 226 227 ruleset_fd = landlock_create_ruleset(&ruleset_attr, 228 sizeof(ruleset_attr), 0); 229 if (ruleset_fd < 0) 230 _exit(1); 231 232 /* First rule: version becomes 1. */ 233 path_beneath.parent_fd = 234 open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 235 if (path_beneath.parent_fd < 0) 236 _exit(1); 237 landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 238 &path_beneath, 0); 239 close(path_beneath.parent_fd); 240 241 /* Second rule: version becomes 2. */ 242 path_beneath.parent_fd = 243 open("/tmp", O_PATH | O_DIRECTORY | O_CLOEXEC); 244 if (path_beneath.parent_fd < 0) 245 _exit(1); 246 landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 247 &path_beneath, 0); 248 close(path_beneath.parent_fd); 249 250 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 251 if (landlock_restrict_self(ruleset_fd, 0)) 252 _exit(1); 253 close(ruleset_fd); 254 _exit(0); 255 } 256 257 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 258 ASSERT_TRUE(WIFEXITED(status)); 259 EXPECT_EQ(0, WEXITSTATUS(status)); 260 261 buf = tracefs_read_buf(); 262 ASSERT_NE(NULL, buf); 263 264 /* Verify create_ruleset has version=0. */ 265 ASSERT_EQ(0, 266 tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 267 "ruleset", field, sizeof(field))); 268 dot = strchr(field, '.'); 269 ASSERT_NE(0, !!dot); 270 EXPECT_STREQ("0", dot + 1); 271 272 /* Verify two add_rule_path_beneath events were emitted. */ 273 EXPECT_EQ(2, tracefs_count_matches( 274 buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) 275 { 276 TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf); 277 } 278 279 /* 280 * Verify create_domain records version=2 (after 2 add_rule calls). The 281 * ruleset field format is <hex_id>.<dec_version>. 282 */ 283 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 284 "ruleset", field, sizeof(field))); 285 dot = strchr(field, '.'); 286 ASSERT_NE(0, !!dot); 287 EXPECT_STREQ("2", dot + 1); 288 289 free(buf); 290 } 291 292 /* 293 * Verifies that landlock_create_domain emits a trace event linking the ruleset 294 * ID to the new domain ID. 295 */ 296 TEST_F(trace, create_domain) 297 { 298 pid_t pid; 299 int status, check_count; 300 char *buf; 301 char parent_id[64], domain_id[64], check_domain[64]; 302 303 /* Clear before the sandboxed child. */ 304 ASSERT_EQ(0, tracefs_clear_buf()); 305 306 pid = fork(); 307 ASSERT_LE(0, pid); 308 309 if (pid == 0) { 310 struct landlock_ruleset_attr ruleset_attr = { 311 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 312 }; 313 struct landlock_path_beneath_attr path_beneath = { 314 .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, 315 }; 316 int ruleset_fd, fd; 317 318 ruleset_fd = landlock_create_ruleset(&ruleset_attr, 319 sizeof(ruleset_attr), 0); 320 if (ruleset_fd < 0) 321 _exit(1); 322 323 path_beneath.parent_fd = 324 open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 325 if (path_beneath.parent_fd < 0) 326 _exit(1); 327 328 landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 329 &path_beneath, 0); 330 close(path_beneath.parent_fd); 331 332 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 333 if (landlock_restrict_self(ruleset_fd, 0)) 334 _exit(1); 335 close(ruleset_fd); 336 337 /* Trigger a check_rule to verify domain_id correlation. */ 338 fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 339 if (fd >= 0) 340 close(fd); 341 342 _exit(0); 343 } 344 345 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 346 ASSERT_TRUE(WIFEXITED(status)); 347 EXPECT_EQ(0, WEXITSTATUS(status)); 348 349 buf = tracefs_read_buf(); 350 ASSERT_NE(NULL, buf); 351 352 /* Verify create_domain event exists. */ 353 EXPECT_EQ(1, 354 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 355 { 356 TH_LOG("Expected 1 create_domain event\n%s", buf); 357 } 358 359 /* Extract the domain ID from create_domain. */ 360 EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 361 "domain", domain_id, 362 sizeof(domain_id))); 363 364 /* Verify domain ID is non-zero. */ 365 EXPECT_NE(0, strcmp(domain_id, "0")); 366 367 /* Verify parent=0 (first restriction, no prior domain). */ 368 EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 369 "parent", parent_id, 370 sizeof(parent_id))); 371 EXPECT_STREQ("0", parent_id); 372 373 /* 374 * Verify the same domain ID appears in the check_rule event, confirming 375 * end-to-end correlation. 376 */ 377 check_count = 378 tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); 379 ASSERT_LE(1, check_count) 380 { 381 TH_LOG("Expected check_rule_inode events\n%s", buf); 382 } 383 384 EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), 385 "domain", check_domain, 386 sizeof(check_domain))); 387 EXPECT_STREQ(domain_id, check_domain); 388 389 free(buf); 390 } 391 392 /* Builds a rule-less scope-based ruleset; returns the fd or -1. */ 393 static int build_enforce_ruleset(void) 394 { 395 const struct landlock_ruleset_attr attr = { 396 .scoped = LANDLOCK_SCOPE_SIGNAL, 397 }; 398 399 return landlock_create_ruleset(&attr, sizeof(attr), 0); 400 } 401 402 /* 403 * Verifies that nested landlock_restrict_self calls produce trace events with 404 * correct parent domain IDs: the second create_domain's parent should be the 405 * first domain's ID. 406 */ 407 TEST_F(trace, create_domain_nested) 408 { 409 pid_t pid; 410 int status; 411 char *buf; 412 const char *after_first; 413 char first_domain[64], first_parent[64], second_parent[64]; 414 415 ASSERT_EQ(0, tracefs_clear_buf()); 416 417 pid = fork(); 418 ASSERT_LE(0, pid); 419 420 if (pid == 0) { 421 int ruleset_fd; 422 423 /* First restriction. */ 424 ruleset_fd = build_enforce_ruleset(); 425 if (ruleset_fd < 0) 426 _exit(1); 427 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 428 if (landlock_restrict_self(ruleset_fd, 0)) 429 _exit(1); 430 close(ruleset_fd); 431 432 /* Second restriction (nested). */ 433 ruleset_fd = build_enforce_ruleset(); 434 if (ruleset_fd < 0) 435 _exit(1); 436 if (landlock_restrict_self(ruleset_fd, 0)) 437 _exit(1); 438 close(ruleset_fd); 439 440 _exit(0); 441 } 442 443 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 444 ASSERT_TRUE(WIFEXITED(status)); 445 EXPECT_EQ(0, WEXITSTATUS(status)); 446 447 buf = tracefs_read_buf(); 448 ASSERT_NE(NULL, buf); 449 450 /* Should have 2 create_domain events. */ 451 EXPECT_EQ(2, 452 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 453 { 454 TH_LOG("Expected 2 create_domain events\n%s", buf); 455 } 456 457 /* 458 * Extract domain and parent from each create_domain event. The first 459 * event (parent=0) is the outer domain; the second (parent!=0) is the 460 * nested domain whose parent should match the first domain's ID. 461 */ 462 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 463 "domain", first_domain, 464 sizeof(first_domain))); 465 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 466 "parent", first_parent, 467 sizeof(first_parent))); 468 EXPECT_STREQ("0", first_parent); 469 470 /* 471 * Find the second create_domain by scanning past the first. 472 * tracefs_extract_field returns the first match, so search in the 473 * buffer after the first event. 474 * 475 * Skip past the first create_domain line. tracefs_extract_field matches 476 * the first line that matches the regex, so passing the buffer after 477 * the first matching line gives us the second event. 478 */ 479 after_first = strstr(buf, "landlock_create_domain:"); 480 ASSERT_NE(NULL, after_first); 481 after_first = strchr(after_first, '\n'); 482 ASSERT_NE(NULL, after_first); 483 484 ASSERT_EQ(0, tracefs_extract_field( 485 after_first + 1, REGEX_CREATE_DOMAIN(TRACE_TASK), 486 "parent", second_parent, sizeof(second_parent))); 487 488 /* The second domain's parent should be the first domain's ID. */ 489 EXPECT_STREQ(first_domain, second_parent); 490 491 free(buf); 492 } 493 494 /* 495 * Verifies that landlock_add_rule does not emit a trace event when the syscall 496 * fails (e.g., invalid ruleset fd). 497 */ 498 TEST_F(trace, add_rule_invalid_fd) 499 { 500 struct landlock_path_beneath_attr path_beneath = { 501 .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE, 502 }; 503 char *buf; 504 505 path_beneath.parent_fd = open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 506 ASSERT_LE(0, path_beneath.parent_fd); 507 508 /* Invalid ruleset fd (-1). */ 509 ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 510 &path_beneath, 0)); 511 ASSERT_EQ(0, close(path_beneath.parent_fd)); 512 513 buf = tracefs_read_buf(); 514 ASSERT_NE(NULL, buf); 515 516 EXPECT_EQ(0, tracefs_count_matches( 517 buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) 518 { 519 TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s", 520 buf); 521 } 522 523 free(buf); 524 } 525 526 /* 527 * Verifies that landlock_create_domain does not emit a trace event when the 528 * syscall fails (e.g., invalid ruleset fd or unknown flags). 529 */ 530 TEST_F(trace, create_domain_invalid) 531 { 532 int ruleset_fd; 533 char *buf; 534 535 ruleset_fd = build_enforce_ruleset(); 536 ASSERT_LE(0, ruleset_fd); 537 538 /* Clear the trace buffer after create_ruleset event. */ 539 ASSERT_EQ(0, tracefs_clear_buf()); 540 541 /* Invalid fd. */ 542 ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); 543 544 /* Unknown flags. */ 545 ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, -1)); 546 547 ASSERT_EQ(0, close(ruleset_fd)); 548 549 buf = tracefs_read_buf(); 550 ASSERT_NE(NULL, buf); 551 552 EXPECT_EQ(0, 553 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 554 { 555 TH_LOG("No create_domain event expected on error\n%s", buf); 556 } 557 558 free(buf); 559 } 560 561 /* 562 * Verifies that trace_landlock_free_domain fires when a domain is deallocated, 563 * with the correct denials count. 564 */ 565 TEST_F(trace, free_domain) 566 { 567 char *buf; 568 int count; 569 char denials_field[32]; 570 571 ASSERT_EQ(0, tracefs_clear_buf()); 572 573 /* 574 * The domain is freed via a work queue (kworker), so the free_domain 575 * trace event is emitted from a different PID. Clear the PID filter 576 * BEFORE the child exits, so the kworker event passes the filter when 577 * it fires. 578 */ 579 set_cap(_metadata, CAP_SYS_ADMIN); 580 tracefs_clear_pid_filter(); 581 clear_cap(_metadata, CAP_SYS_ADMIN); 582 583 sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 584 LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 585 586 /* 587 * Wait for the deferred deallocation work to run. The domain is freed 588 * asynchronously from a kworker; poll until the event appears or a 589 * timeout is reached. 590 */ 591 for (int retry = 0; retry < 10; retry++) { 592 usleep(100000); 593 594 set_cap(_metadata, CAP_SYS_ADMIN); 595 buf = tracefs_read_trace(); 596 clear_cap(_metadata, CAP_SYS_ADMIN); 597 ASSERT_NE(NULL, buf); 598 599 count = tracefs_count_matches(buf, 600 REGEX_FREE_DOMAIN(KWORKER_TASK)); 601 if (count >= 1) 602 break; 603 free(buf); 604 buf = NULL; 605 } 606 607 set_cap(_metadata, CAP_SYS_ADMIN); 608 ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 609 clear_cap(_metadata, CAP_SYS_ADMIN); 610 611 ASSERT_NE(NULL, buf); 612 EXPECT_LE(1, count) 613 { 614 TH_LOG("Expected free_domain event, got %d\n%s", count, buf); 615 } 616 617 /* Verify denials count matches the single denial we triggered. */ 618 EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK), 619 "denials", denials_field, 620 sizeof(denials_field))); 621 EXPECT_STREQ("1", denials_field); 622 623 free(buf); 624 } 625 626 /* 627 * Verifies that deny_access_fs includes the enriched fields: same_exec and 628 * logged. 629 */ 630 TEST_F(trace, deny_access_fs_fields) 631 { 632 char *buf; 633 char field_buf[64]; 634 635 ASSERT_EQ(0, tracefs_clear_buf()); 636 637 /* Trigger a denial: rule for /usr, access /tmp. */ 638 sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 639 LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 640 641 buf = tracefs_read_buf(); 642 ASSERT_NE(NULL, buf); 643 644 /* Verify the enriched fields are present and have valid values. */ 645 ASSERT_EQ(0, tracefs_extract_field( 646 buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "same_exec", 647 field_buf, sizeof(field_buf))); 648 /* Child is the same exec that restricted itself. */ 649 EXPECT_STREQ("1", field_buf); 650 651 /* Same exec with default flags: audit would log this denial. */ 652 ASSERT_EQ(0, tracefs_extract_field( 653 buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "logged", 654 field_buf, sizeof(field_buf))); 655 EXPECT_STREQ("1", field_buf); 656 657 free(buf); 658 } 659 660 /* 661 * Verifies that same_exec is 1 (true) for denials from the same executable that 662 * called landlock_restrict_self(). 663 */ 664 TEST_F(trace, same_exec_before_exec) 665 { 666 pid_t pid; 667 int status; 668 char *buf; 669 char field[64]; 670 671 ASSERT_EQ(0, tracefs_clear_buf()); 672 673 pid = fork(); 674 ASSERT_LE(0, pid); 675 676 if (pid == 0) { 677 struct landlock_ruleset_attr attr = { 678 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 679 }; 680 int ruleset_fd, dir_fd; 681 682 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 683 if (ruleset_fd < 0) 684 _exit(1); 685 686 /* No rules: all read_dir access is denied. */ 687 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 688 if (landlock_restrict_self(ruleset_fd, 0)) 689 _exit(1); 690 close(ruleset_fd); 691 692 /* Trigger denial without exec (same executable). */ 693 dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 694 if (dir_fd >= 0) 695 close(dir_fd); 696 _exit(0); 697 } 698 699 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 700 ASSERT_TRUE(WIFEXITED(status)); 701 EXPECT_EQ(0, WEXITSTATUS(status)); 702 703 buf = tracefs_read_buf(); 704 ASSERT_NE(NULL, buf); 705 706 /* Should have at least one deny_access_fs denial. */ 707 EXPECT_LE(1, 708 tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))); 709 710 /* Verify same_exec=1 (same executable, no exec). */ 711 ASSERT_EQ(0, 712 tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 713 "same_exec", field, sizeof(field))); 714 EXPECT_STREQ("1", field); 715 716 /* Same exec with default flags: audit would log this denial. */ 717 ASSERT_EQ(0, 718 tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 719 "logged", field, sizeof(field))); 720 EXPECT_STREQ("1", field); 721 722 free(buf); 723 } 724 725 /* 726 * Verifies that same_exec is 0 (false) for denials from a process that has 727 * exec'd a new binary after landlock_restrict_self(). The sandboxed child 728 * exec's true which opens "." and triggers a read_dir denial. Covers the 729 * "trace-only" visibility condition: with same_exec=0 and the default 730 * log_new_exec=0, audit suppresses the denial (logged=0) but the trace event 731 * still fires. 732 */ 733 TEST_F(trace, same_exec_after_exec) 734 { 735 char *buf; 736 char field[64]; 737 738 ASSERT_EQ(0, tracefs_clear_buf()); 739 740 sandbox_child_exec_true(_metadata, 0); 741 742 buf = tracefs_read_buf(); 743 ASSERT_NE(NULL, buf); 744 745 EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true"))); 746 747 /* Verify same_exec=0 (different executable after exec). */ 748 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 749 "same_exec", field, sizeof(field))); 750 EXPECT_STREQ("0", field); 751 752 /* 753 * same_exec=0 with default log_new_exec=0: audit suppresses (logged=0). 754 */ 755 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 756 "logged", field, sizeof(field))); 757 EXPECT_STREQ("0", field); 758 759 free(buf); 760 } 761 762 /* 763 * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF suppresses logging 764 * (logged=0) for a denial from the same executable. 765 */ 766 TEST_F(trace, log_flags_same_exec_off) 767 { 768 pid_t pid; 769 int status; 770 char *buf; 771 char field[64]; 772 773 ASSERT_EQ(0, tracefs_clear_buf()); 774 775 pid = fork(); 776 ASSERT_LE(0, pid); 777 778 if (pid == 0) { 779 struct landlock_ruleset_attr attr = { 780 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 781 }; 782 int ruleset_fd, dir_fd; 783 784 ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 785 if (ruleset_fd < 0) 786 _exit(1); 787 788 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 789 if (landlock_restrict_self( 790 ruleset_fd, 791 LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF)) 792 _exit(1); 793 close(ruleset_fd); 794 795 dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 796 if (dir_fd >= 0) 797 close(dir_fd); 798 _exit(0); 799 } 800 801 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 802 ASSERT_TRUE(WIFEXITED(status)); 803 EXPECT_EQ(0, WEXITSTATUS(status)); 804 805 buf = tracefs_read_buf(); 806 ASSERT_NE(NULL, buf); 807 808 EXPECT_LE(1, 809 tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))); 810 811 /* Same-exec denial with LOG_SAME_EXEC_OFF: audit suppresses it. */ 812 ASSERT_EQ(0, 813 tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 814 "logged", field, sizeof(field))); 815 EXPECT_STREQ("0", field); 816 817 free(buf); 818 } 819 820 /* 821 * Verifies that LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON causes a post-exec 822 * denial to be logged (logged=1). The child exec's true so that the denial 823 * comes from a new executable (same_exec=0). 824 */ 825 TEST_F(trace, log_flags_new_exec_on) 826 { 827 char *buf; 828 char field[64]; 829 830 ASSERT_EQ(0, tracefs_clear_buf()); 831 832 sandbox_child_exec_true(_metadata, 833 LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); 834 835 buf = tracefs_read_buf(); 836 ASSERT_NE(NULL, buf); 837 838 EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true"))); 839 840 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 841 "same_exec", field, sizeof(field))); 842 EXPECT_STREQ("0", field); 843 844 /* LOG_NEW_EXEC_ON: the post-exec denial (same_exec=0) is logged. */ 845 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 846 "logged", field, sizeof(field))); 847 EXPECT_STREQ("1", field); 848 849 free(buf); 850 } 851 852 /* 853 * Verifies that denials suppressed by audit log flags are still counted in 854 * num_denials. The child restricts itself with default flags (log_same_exec=1, 855 * log_new_exec=0), then execs true which attempts to read a denied directory. 856 * After exec, same_exec=0 and log_new_exec=0, so audit suppresses the denial. 857 * But the trace event fires unconditionally and free_domain must report the 858 * correct denials count. 859 */ 860 TEST_F(trace, non_audit_visible_denial_counting) 861 { 862 char *buf = NULL; 863 char denials_field[32]; 864 int count; 865 866 set_cap(_metadata, CAP_SYS_ADMIN); 867 ASSERT_EQ(0, tracefs_clear()); 868 tracefs_clear_pid_filter(); 869 clear_cap(_metadata, CAP_SYS_ADMIN); 870 871 sandbox_child_exec_true(_metadata, 0); 872 873 /* Wait for free_domain event with retry. */ 874 for (int retry = 0; retry < 10; retry++) { 875 usleep(100000); 876 877 set_cap(_metadata, CAP_SYS_ADMIN); 878 buf = tracefs_read_trace(); 879 clear_cap(_metadata, CAP_SYS_ADMIN); 880 if (!buf) 881 break; 882 883 count = tracefs_count_matches(buf, 884 REGEX_FREE_DOMAIN(KWORKER_TASK)); 885 if (count >= 1) 886 break; 887 free(buf); 888 buf = NULL; 889 } 890 891 set_cap(_metadata, CAP_SYS_ADMIN); 892 ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 893 clear_cap(_metadata, CAP_SYS_ADMIN); 894 895 /* 896 * The denial happened after exec (same_exec=0), so audit would suppress 897 * it. But num_denials counts all denials regardless. 898 */ 899 ASSERT_NE(NULL, buf) 900 { 901 TH_LOG("free_domain event not found after 10 retries"); 902 } 903 EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK), 904 "denials", denials_field, 905 sizeof(denials_field))); 906 EXPECT_STREQ("1", denials_field); 907 908 free(buf); 909 } 910 911 /* 912 * Verifies that landlock_add_rule_net_port emits a trace event with the correct 913 * port and allowed access mask fields. 914 */ 915 TEST_F(trace, add_rule_net_port_fields) 916 { 917 struct landlock_ruleset_attr ruleset_attr = { 918 .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, 919 }; 920 struct landlock_net_port_attr net_port = { 921 .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, 922 .port = 8080, 923 }; 924 int ruleset_fd; 925 char *buf; 926 char field[64]; 927 928 ruleset_fd = 929 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 930 ASSERT_LE(0, ruleset_fd); 931 932 ASSERT_EQ(0, tracefs_clear_buf()); 933 934 ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, 935 &net_port, 0)); 936 close(ruleset_fd); 937 938 buf = tracefs_read_buf(); 939 ASSERT_NE(NULL, buf); 940 941 EXPECT_EQ(1, tracefs_count_matches(buf, 942 REGEX_ADD_RULE_NET_PORT(TRACE_TASK))) 943 { 944 TH_LOG("Expected 1 add_rule_net_port event\n%s", buf); 945 } 946 947 /* 948 * Verify the port is in host endianness, matching the UAPI convention 949 * (landlock_net_port_attr.port). On little-endian, htons(8080) is 950 * 36895, so this comparison catches byte-order bugs. 951 */ 952 EXPECT_EQ(0, tracefs_extract_field(buf, 953 REGEX_ADD_RULE_NET_PORT(TRACE_TASK), 954 "port", field, sizeof(field))); 955 EXPECT_STREQ("8080", field); 956 /* 957 * The allowed mask is the absolute value after transformation: the 958 * user-requested BIND_TCP plus all unhandled access rights (the other 959 * net access bits are unhandled because the ruleset only handles 960 * BIND_TCP). 961 */ 962 EXPECT_EQ(0, tracefs_extract_field( 963 buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK), 964 "access_rights", field, sizeof(field))); 965 EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field); 966 967 free(buf); 968 } 969 970 /* 971 * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF suppresses audit 972 * logging for child domains (logged=0) even though the child's own 973 * per-execution flags are the defaults, while the trace event still fires 974 * (tracing is unconditional). The parent creates a domain with 975 * LOG_SUBDOMAINS_OFF, then the child creates a sub-domain and triggers a 976 * denial. 977 */ 978 TEST_F(trace, log_flags_subdomains_off) 979 { 980 pid_t pid; 981 int status; 982 char *buf; 983 char field[64]; 984 985 ASSERT_EQ(0, tracefs_clear_buf()); 986 987 pid = fork(); 988 ASSERT_LE(0, pid); 989 990 if (pid == 0) { 991 struct landlock_ruleset_attr attr = { 992 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 993 }; 994 int parent_fd, child_fd, dir_fd; 995 996 /* Parent domain with LOG_SUBDOMAINS_OFF. */ 997 parent_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 998 if (parent_fd < 0) 999 _exit(1); 1000 1001 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1002 if (landlock_restrict_self( 1003 parent_fd, 1004 LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) 1005 _exit(1); 1006 close(parent_fd); 1007 1008 /* Child sub-domain with default flags. */ 1009 child_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 1010 if (child_fd < 0) 1011 _exit(1); 1012 1013 if (landlock_restrict_self(child_fd, 0)) 1014 _exit(1); 1015 close(child_fd); 1016 1017 /* Trigger a denial from the child domain. */ 1018 dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 1019 if (dir_fd >= 0) 1020 close(dir_fd); 1021 _exit(0); 1022 } 1023 1024 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1025 ASSERT_TRUE(WIFEXITED(status)); 1026 EXPECT_EQ(0, WEXITSTATUS(status)); 1027 1028 buf = tracefs_read_buf(); 1029 ASSERT_NE(NULL, buf); 1030 1031 /* 1032 * Trace fires unconditionally even though audit is disabled for the 1033 * child domain (parent had LOG_SUBDOMAINS_OFF). 1034 */ 1035 EXPECT_LE(1, 1036 tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))) 1037 { 1038 TH_LOG("Expected deny_access_fs event despite " 1039 "LOG_SUBDOMAINS_OFF\n%s", 1040 buf); 1041 } 1042 1043 /* 1044 * The child's per-execution flags default to logging, but the 1045 * ancestor's LOG_SUBDOMAINS_OFF disables it, so audit suppresses this 1046 * denial (logged=0). This is exactly the case the single logged field 1047 * captures and the raw per-execution flags could not. 1048 */ 1049 ASSERT_EQ(0, 1050 tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 1051 "logged", field, sizeof(field))); 1052 EXPECT_STREQ("0", field); 1053 1054 free(buf); 1055 } 1056 1057 /* Verifies that landlock_free_ruleset fires when a ruleset FD is closed. */ 1058 TEST_F(trace, free_ruleset_on_close) 1059 { 1060 struct landlock_ruleset_attr ruleset_attr = { 1061 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 1062 }; 1063 int ruleset_fd; 1064 char *buf; 1065 1066 ruleset_fd = 1067 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 1068 ASSERT_LE(0, ruleset_fd); 1069 1070 ASSERT_EQ(0, tracefs_clear_buf()); 1071 1072 /* Closing the FD should trigger free_ruleset. */ 1073 close(ruleset_fd); 1074 1075 buf = tracefs_read_buf(); 1076 ASSERT_NE(NULL, buf); 1077 1078 EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_FREE_RULESET(TRACE_TASK))) 1079 { 1080 TH_LOG("Expected 1 free_ruleset event\n%s", buf); 1081 } 1082 1083 free(buf); 1084 } 1085 1086 /* 1087 * Counts landlock_enforce_domain lines, filtered by @domain (NULL matches any), 1088 * @complete and @process_wide (a negative value matches any). Builds the 1089 * anchored regex dynamically so a single helper covers every field assertion. 1090 */ 1091 static int count_enforce_matches(const char *buf, const char *domain, 1092 int complete, int process_wide, 1093 int no_new_privs) 1094 { 1095 char pattern[512], dom[80], comp[8], pw[8], nnp[8]; 1096 1097 if (domain) 1098 snprintf(dom, sizeof(dom), "%s", domain); 1099 else 1100 snprintf(dom, sizeof(dom), "[0-9a-f]\\+"); 1101 if (complete < 0) 1102 snprintf(comp, sizeof(comp), "[01]"); 1103 else 1104 snprintf(comp, sizeof(comp), "%d", complete); 1105 if (process_wide < 0) 1106 snprintf(pw, sizeof(pw), "[01]"); 1107 else 1108 snprintf(pw, sizeof(pw), "%d", process_wide); 1109 if (no_new_privs < 0) 1110 snprintf(nnp, sizeof(nnp), "[01]"); 1111 else 1112 snprintf(nnp, sizeof(nnp), "%d", no_new_privs); 1113 1114 snprintf(pattern, sizeof(pattern), 1115 TRACE_PREFIX(TRACE_TASK) "landlock_enforce_domain: " 1116 "domain=%s " 1117 "complete=%s process_wide=%s " 1118 "no_new_privs=%s$", 1119 dom, comp, pw, nnp); 1120 return tracefs_count_matches(buf, pattern); 1121 } 1122 1123 /* Idle sibling: waits on the barrier so it is a live thread, then sleeps. */ 1124 static void *enforce_idle(void *arg) 1125 { 1126 pthread_barrier_t *barrier = arg; 1127 1128 pthread_barrier_wait(barrier); 1129 while (true) 1130 sleep(1); 1131 return NULL; 1132 } 1133 1134 /* 1135 * Child body: spawns @nthreads idle siblings (barrier-synchronized so they are 1136 * live when the syscall runs), then enforces a domain with @flags. Returns 0 1137 * on success; the process exits afterwards, reaping the siblings. 1138 */ 1139 static int child_enforce(int nthreads, __u32 flags) 1140 { 1141 pthread_t threads[8]; 1142 pthread_barrier_t barrier; 1143 int ruleset_fd, i; 1144 1145 if (nthreads > 0) { 1146 if (pthread_barrier_init(&barrier, NULL, nthreads + 1)) 1147 return 1; 1148 for (i = 0; i < nthreads; i++) 1149 if (pthread_create(&threads[i], NULL, enforce_idle, 1150 &barrier)) 1151 return 1; 1152 pthread_barrier_wait(&barrier); 1153 } 1154 1155 ruleset_fd = build_enforce_ruleset(); 1156 if (ruleset_fd < 0) 1157 return 1; 1158 1159 /* 1160 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs itself, so skip 1161 * the prctl() to exercise that path; otherwise Landlock requires 1162 * no_new_privs up front. 1163 */ 1164 if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)) 1165 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1166 if (landlock_restrict_self(ruleset_fd, flags)) 1167 return 1; 1168 close(ruleset_fd); 1169 return 0; 1170 } 1171 1172 /* 1173 * Runs in a spawned thread after the group leader called pthread_exit(). The 1174 * leader lingers as an un-reaped zombie, so get_nr_threads() still counts it 1175 * and this non-leader is not the only thread; enforcing here therefore reports 1176 * process_wide=0. 1177 */ 1178 static void *enforce_nonleader(void *arg) 1179 { 1180 int ruleset_fd; 1181 1182 ruleset_fd = build_enforce_ruleset(); 1183 if (ruleset_fd < 0) 1184 _exit(1); 1185 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1186 if (landlock_restrict_self(ruleset_fd, 0)) 1187 _exit(1); 1188 _exit(0); 1189 } 1190 1191 /* 1192 * Collapses the enforce_domain field cases into one parametrized test. Each 1193 * variant runs child_enforce(nthreads, flags) and checks the resulting 1194 * enforce_domain events. The flags column also selects how no_new_privs is 1195 * set: with LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS child_enforce() skips the 1196 * prctl() so the flag sets it (and, with TSYNC, propagates to the siblings); 1197 * otherwise a prior prctl() sets it on the caller (and TSYNC propagates that). 1198 */ 1199 1200 /* clang-format off */ 1201 FIXTURE(trace_enforce) { 1202 /* clang-format on */ 1203 int tracefs_ok; 1204 }; 1205 1206 FIXTURE_SETUP(trace_enforce) 1207 { 1208 int ret; 1209 1210 set_cap(_metadata, CAP_SYS_ADMIN); 1211 ASSERT_EQ(0, unshare(CLONE_NEWNS)); 1212 ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); 1213 1214 ret = tracefs_fixture_setup(); 1215 if (ret) { 1216 clear_cap(_metadata, CAP_SYS_ADMIN); 1217 self->tracefs_ok = 0; 1218 SKIP(return, "tracefs not available"); 1219 } 1220 self->tracefs_ok = 1; 1221 1222 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); 1223 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); 1224 ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); 1225 ASSERT_EQ(0, tracefs_clear()); 1226 clear_cap(_metadata, CAP_SYS_ADMIN); 1227 } 1228 1229 FIXTURE_TEARDOWN(trace_enforce) 1230 { 1231 if (!self->tracefs_ok) 1232 return; 1233 1234 set_cap(_metadata, CAP_SYS_ADMIN); 1235 tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); 1236 tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); 1237 tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); 1238 tracefs_fixture_teardown(); 1239 clear_cap(_metadata, CAP_SYS_ADMIN); 1240 } 1241 1242 /* clang-format off */ 1243 FIXTURE_VARIANT(trace_enforce) { 1244 /* clang-format on */ 1245 /* Inputs to child_enforce(). */ 1246 int nthreads; 1247 __u32 flags; 1248 /* Expected enforce_domain event counts. */ 1249 int total; 1250 int complete; 1251 int process_wide; 1252 int no_new_privs; 1253 }; 1254 1255 /* clang-format off */ 1256 1257 /* Single thread, no flags: prctl-backed no_new_privs. */ 1258 FIXTURE_VARIANT_ADD(trace_enforce, single) { 1259 .nthreads = 0, .flags = 0, 1260 .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1261 }; 1262 1263 /* Single thread: the NO_NEW_PRIVS flag sets no_new_privs (no prctl). */ 1264 FIXTURE_VARIANT_ADD(trace_enforce, no_new_privs) { 1265 .nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, 1266 .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1267 }; 1268 1269 /* TSYNC on a lone thread still concludes, process-wide. */ 1270 FIXTURE_VARIANT_ADD(trace_enforce, tsync_single) { 1271 .nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_TSYNC, 1272 .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1273 }; 1274 1275 /* TSYNC sweeps N siblings; the caller's prctl-backed nnp propagates to all. */ 1276 FIXTURE_VARIANT_ADD(trace_enforce, tsync_multithread) { 1277 .nthreads = 3, .flags = LANDLOCK_RESTRICT_SELF_TSYNC, 1278 .total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4, 1279 }; 1280 1281 /* TSYNC + NO_NEW_PRIVS flag sets nnp on the caller and every swept sibling. */ 1282 FIXTURE_VARIANT_ADD(trace_enforce, tsync_no_new_privs) { 1283 .nthreads = 3, 1284 .flags = LANDLOCK_RESTRICT_SELF_TSYNC | LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, 1285 .total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4, 1286 }; 1287 1288 /* Non-TSYNC on a multi-threaded process enforces only the caller. */ 1289 FIXTURE_VARIANT_ADD(trace_enforce, multithread_non_tsync) { 1290 .nthreads = 3, .flags = 0, 1291 .total = 1, .complete = 1, .process_wide = 0, .no_new_privs = 1, 1292 }; 1293 1294 /* clang-format on */ 1295 1296 /* 1297 * One create_domain and variant->total enforce_domain events sharing that 1298 * domain ID; complete=1 marks the single concluding event, and the process_wide 1299 * / no_new_privs counts match the variant. Counts are order-independent, 1300 * evaluated after the syscall returns. 1301 */ 1302 TEST_F(trace_enforce, enforce) 1303 { 1304 pid_t pid; 1305 int status; 1306 char *buf; 1307 char domain[64]; 1308 1309 ASSERT_EQ(0, tracefs_clear_buf()); 1310 1311 pid = fork(); 1312 ASSERT_LE(0, pid); 1313 if (pid == 0) 1314 _exit(child_enforce(variant->nthreads, variant->flags)); 1315 1316 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1317 ASSERT_TRUE(WIFEXITED(status)); 1318 EXPECT_EQ(0, WEXITSTATUS(status)); 1319 1320 buf = tracefs_read_buf(); 1321 ASSERT_NE(NULL, buf); 1322 1323 EXPECT_EQ(1, 1324 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1325 EXPECT_EQ(variant->total, count_enforce_matches(buf, NULL, -1, -1, -1)) 1326 { 1327 TH_LOG("Expected %d enforce_domain events\n%s", variant->total, 1328 buf); 1329 } 1330 EXPECT_EQ(variant->complete, 1331 count_enforce_matches(buf, NULL, 1, -1, -1)); 1332 EXPECT_EQ(variant->total - variant->complete, 1333 count_enforce_matches(buf, NULL, 0, -1, -1)); 1334 EXPECT_EQ(variant->process_wide, 1335 count_enforce_matches(buf, NULL, -1, 1, -1)); 1336 EXPECT_EQ(variant->total - variant->process_wide, 1337 count_enforce_matches(buf, NULL, -1, 0, -1)); 1338 EXPECT_EQ(variant->no_new_privs, 1339 count_enforce_matches(buf, NULL, -1, -1, 1)); 1340 1341 ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 1342 "domain", domain, sizeof(domain))); 1343 EXPECT_EQ(variant->total, 1344 count_enforce_matches(buf, domain, -1, -1, -1)); 1345 1346 free(buf); 1347 } 1348 1349 /* 1350 * A non-leader thread enforcing a domain while the group leader lingers as an 1351 * un-reaped zombie reports process_wide=0: get_nr_threads() counts the zombie 1352 * leader, so the group is not single-threaded. This is the reachable half of 1353 * the caveat that process_wide==0 never proves the process is multi-threaded 1354 * (get_nr_threads(), unlike the leader-relative thread_group_empty(), counts 1355 * the zombie leader). 1356 */ 1357 TEST_F(trace, enforce_single_non_leader) 1358 { 1359 pid_t pid; 1360 int status; 1361 char *buf; 1362 1363 ASSERT_EQ(0, tracefs_clear_buf()); 1364 1365 pid = fork(); 1366 ASSERT_LE(0, pid); 1367 if (pid == 0) { 1368 pthread_t worker; 1369 1370 if (pthread_create(&worker, NULL, enforce_nonleader, NULL)) 1371 _exit(1); 1372 /* Leader leaves; the worker enforces as a non-leader. */ 1373 pthread_exit(NULL); 1374 } 1375 1376 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1377 ASSERT_TRUE(WIFEXITED(status)); 1378 EXPECT_EQ(0, WEXITSTATUS(status)); 1379 1380 buf = tracefs_read_buf(); 1381 ASSERT_NE(NULL, buf); 1382 1383 EXPECT_EQ(1, 1384 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1385 EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 0, -1)) 1386 { 1387 TH_LOG("Expected complete=1 process_wide=0 for non-leader\n%s", 1388 buf); 1389 } 1390 1391 free(buf); 1392 } 1393 1394 /* 1395 * Verifies the flags-only path (ruleset_fd == -1) creates no domain and emits 1396 * neither create_domain nor enforce_domain, with and without TSYNC. 1397 */ 1398 TEST_F(trace, enforce_flags_only) 1399 { 1400 pid_t pid; 1401 int status; 1402 char *buf; 1403 1404 ASSERT_EQ(0, tracefs_clear_buf()); 1405 1406 pid = fork(); 1407 ASSERT_LE(0, pid); 1408 if (pid == 0) { 1409 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1410 if (landlock_restrict_self( 1411 -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) 1412 _exit(1); 1413 if (landlock_restrict_self( 1414 -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 1415 LANDLOCK_RESTRICT_SELF_TSYNC)) 1416 _exit(1); 1417 _exit(0); 1418 } 1419 1420 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1421 ASSERT_TRUE(WIFEXITED(status)); 1422 EXPECT_EQ(0, WEXITSTATUS(status)); 1423 1424 buf = tracefs_read_buf(); 1425 ASSERT_NE(NULL, buf); 1426 1427 EXPECT_EQ(0, 1428 tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1429 EXPECT_EQ(0, count_enforce_matches(buf, NULL, -1, -1, -1)) 1430 { 1431 TH_LOG("No enforce_domain expected on flags-only path\n%s", 1432 buf); 1433 } 1434 1435 free(buf); 1436 } 1437 1438 static void enforce_nop_handler(int sig) 1439 { 1440 } 1441 1442 struct abort_signaler_data { 1443 pthread_t target; 1444 volatile bool stop; 1445 }; 1446 1447 /* 1448 * Hammers the target thread with SIGUSR1 to interrupt the TSYNC prepare wait. 1449 */ 1450 static void *abort_signaler(void *arg) 1451 { 1452 struct abort_signaler_data *data = arg; 1453 1454 while (!data->stop) 1455 pthread_kill(data->target, SIGUSR1); 1456 return NULL; 1457 } 1458 1459 /* 1460 * Child body for the abort test: with idle siblings and a signaler interrupting 1461 * it, repeatedly enforces under TSYNC. An interrupted attempt aborts its 1462 * just-created domain (create_domain + free_domain, zero enforce_domain) while 1463 * -ERESTARTNOINTR transparently restarts the syscall, so a successful retry may 1464 * add its own full lifecycle. 1465 */ 1466 static int child_abort(int nsiblings, int attempts) 1467 { 1468 pthread_t threads[200]; 1469 pthread_t signaler; 1470 pthread_barrier_t barrier; 1471 struct abort_signaler_data data = {}; 1472 struct sigaction sa = {}; 1473 int i; 1474 1475 sa.sa_handler = enforce_nop_handler; 1476 if (sigaction(SIGUSR1, &sa, NULL)) 1477 return 1; 1478 1479 if (pthread_barrier_init(&barrier, NULL, nsiblings + 1)) 1480 return 1; 1481 for (i = 0; i < nsiblings; i++) 1482 if (pthread_create(&threads[i], NULL, enforce_idle, &barrier)) 1483 return 1; 1484 pthread_barrier_wait(&barrier); 1485 1486 data.target = pthread_self(); 1487 if (pthread_create(&signaler, NULL, abort_signaler, &data)) 1488 return 1; 1489 1490 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1491 for (i = 0; i < attempts; i++) { 1492 int ruleset_fd = build_enforce_ruleset(); 1493 1494 if (ruleset_fd < 0) 1495 break; 1496 /* 1497 * Ignore the result: an abort returns an error, that is fine. 1498 */ 1499 landlock_restrict_self(ruleset_fd, 1500 LANDLOCK_RESTRICT_SELF_TSYNC); 1501 close(ruleset_fd); 1502 } 1503 1504 data.stop = true; 1505 pthread_join(signaler, NULL); 1506 return 0; 1507 } 1508 1509 /* 1510 * Verifies the abort contract: a domain aborted by a thread-sync failure emits 1511 * create_domain and free_domain but zero enforce_domain. The signal race is 1512 * probabilistic and -ERESTARTNOINTR may add a successful retry's lifecycle, so 1513 * events are grouped by domain ID and the test SKIPs if no abort occurred. 1514 */ 1515 TEST_F(trace, enforce_abort) 1516 { 1517 pid_t pid; 1518 int status, retry; 1519 char *buf = NULL; 1520 const char *cursor; 1521 char domain[64]; 1522 bool abort_found = false; 1523 1524 ASSERT_EQ(0, tracefs_clear_buf()); 1525 1526 /* free_domain fires from a kworker, so widen the filter first. */ 1527 set_cap(_metadata, CAP_SYS_ADMIN); 1528 tracefs_clear_pid_filter(); 1529 clear_cap(_metadata, CAP_SYS_ADMIN); 1530 1531 pid = fork(); 1532 ASSERT_LE(0, pid); 1533 if (pid == 0) 1534 /* 1535 * Match tsync_test's NUM_IDLE_THREADS: enough siblings that 1536 * credential preparation runs in several serialized waves, 1537 * giving the signaler a window to interrupt the thread-sync 1538 * wait and abort the operation. A handful of threads finishes 1539 * in a single wave, leaving no window (the abort never fires). 1540 */ 1541 _exit(child_abort(200, 8)); 1542 1543 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1544 ASSERT_TRUE(WIFEXITED(status)); 1545 EXPECT_EQ(0, WEXITSTATUS(status)); 1546 1547 /* Poll for the asynchronous free_domain events. */ 1548 for (retry = 0; retry < 10; retry++) { 1549 usleep(100000); 1550 set_cap(_metadata, CAP_SYS_ADMIN); 1551 free(buf); 1552 buf = tracefs_read_trace(); 1553 clear_cap(_metadata, CAP_SYS_ADMIN); 1554 ASSERT_NE(NULL, buf); 1555 } 1556 1557 set_cap(_metadata, CAP_SYS_ADMIN); 1558 ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 1559 clear_cap(_metadata, CAP_SYS_ADMIN); 1560 1561 /* 1562 * Walk every create_domain and look for one whose domain ID has zero 1563 * enforce_domain events but a matching free_domain: that is an aborted 1564 * domain (created, never enforced, freed). 1565 */ 1566 cursor = buf; 1567 while (tracefs_extract_field(cursor, REGEX_CREATE_DOMAIN(TRACE_TASK), 1568 "domain", domain, sizeof(domain)) == 0) { 1569 const char *cd, *nl; 1570 char free_pattern[256]; 1571 1572 if (count_enforce_matches(buf, domain, -1, -1, -1) == 0) { 1573 snprintf( 1574 free_pattern, sizeof(free_pattern), 1575 TRACE_PREFIX( 1576 KWORKER_TASK) "landlock_free_domain: " 1577 "domain=%s denials=[0-9]\\+$", 1578 domain); 1579 if (tracefs_count_matches(buf, free_pattern) >= 1) 1580 abort_found = true; 1581 } 1582 1583 cd = strstr(cursor, "landlock_create_domain:"); 1584 if (!cd) 1585 break; 1586 nl = strchr(cd, '\n'); 1587 if (!nl) 1588 break; 1589 cursor = nl + 1; 1590 } 1591 1592 if (!abort_found) { 1593 free(buf); 1594 SKIP(return, "signal race did not produce a thread-sync abort"); 1595 } 1596 1597 free(buf); 1598 } 1599 1600 /* 1601 * The following tests are intentionally elided because the underlying kernel 1602 * mechanisms are already validated by audit tests: 1603 * 1604 * - Domain ID monotonicity: validated by audit_test.c:layers. The same 1605 * landlock_get_id_range() function serves both audit and trace. 1606 * 1607 * - Domain deallocation order (LIFO): validated by audit_test.c:layers. Trace 1608 * events fire from the same free_domain_work() code path. 1609 * 1610 * - Max-layer stacking (16 domains): validated by audit_test.c:layers. 1611 * 1612 * - IPv6 network tests: IPv6 hook dispatch uses the same 1613 * current_check_access_socket() as IPv4, validated by net_test.c:audit tests. 1614 * 1615 * - Per-access-right full matrix (all 16 FS rights): hook dispatch is validated 1616 * by fs_test.c:audit tests. Trace tests verify representative samples to 1617 * ensure bitmask encoding is correct. 1618 * 1619 * - Combined log flag variants (e.g., LOG_SUBDOMAINS_OFF + LOG_NEW_EXEC_ON): 1620 * individual flag tests above cover each flag's effect on trace fields. Flag 1621 * combination logic is validated by audit_test.c:audit_flags tests. 1622 * 1623 * - fs.refer multi-record denials and fs.change_topology (mount): 1624 * trace_denial() uses the same code path for all FS request types. The 1625 * DENTRY union member is validated by the deny_access_fs_fields 1626 * test. Audit tests in fs_test.c cover refer and mount denial specifics. 1627 */ 1628 1629 TEST_HARNESS_MAIN 1630