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