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