Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/landlock: Test audit with restrict flags

Add audit_exec tests to filter Landlock denials according to
cross-execution or muted subdomains.

Add a wait-pipe-sandbox.c test program to sandbox itself and send a
(denied) signals to its parent.

Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Link: https://lore.kernel.org/r/20250320190717.2287696-24-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+357 -1
+1
tools/testing/selftests/landlock/.gitignore
··· 2 2 /sandbox-and-launch 3 3 /true 4 4 /wait-pipe 5 + /wait-pipe-sandbox
+5 -1
tools/testing/selftests/landlock/Makefile
··· 10 10 11 11 TEST_GEN_PROGS := $(src_test:.c=) 12 12 13 - TEST_GEN_PROGS_EXTENDED := true sandbox-and-launch wait-pipe 13 + TEST_GEN_PROGS_EXTENDED := \ 14 + true \ 15 + sandbox-and-launch \ 16 + wait-pipe \ 17 + wait-pipe-sandbox 14 18 15 19 # Short targets: 16 20 $(TEST_GEN_PROGS): LDLIBS += -lcap -lpthread
+219
tools/testing/selftests/landlock/audit_test.c
··· 7 7 8 8 #define _GNU_SOURCE 9 9 #include <errno.h> 10 + #include <limits.h> 10 11 #include <linux/landlock.h> 12 + #include <stdlib.h> 11 13 #include <sys/mount.h> 12 14 #include <sys/prctl.h> 13 15 #include <sys/types.h> ··· 329 327 &audit_tv_default, 330 328 sizeof(audit_tv_default))); 331 329 } 330 + } 331 + 332 + static int matches_log_fs_read_root(int audit_fd) 333 + { 334 + return audit_match_record( 335 + audit_fd, AUDIT_LANDLOCK_ACCESS, 336 + REGEX_LANDLOCK_PREFIX 337 + " blockers=fs\\.read_dir path=\"/\" dev=\"[^\"]\\+\" ino=[0-9]\\+$", 338 + NULL); 339 + } 340 + 341 + FIXTURE(audit_exec) 342 + { 343 + struct audit_filter audit_filter; 344 + int audit_fd; 345 + }; 346 + 347 + FIXTURE_VARIANT(audit_exec) 348 + { 349 + const int restrict_flags; 350 + }; 351 + 352 + /* clang-format off */ 353 + FIXTURE_VARIANT_ADD(audit_exec, default) { 354 + /* clang-format on */ 355 + .restrict_flags = 0, 356 + }; 357 + 358 + /* clang-format off */ 359 + FIXTURE_VARIANT_ADD(audit_exec, same_exec_off) { 360 + /* clang-format on */ 361 + .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, 362 + }; 363 + 364 + /* clang-format off */ 365 + FIXTURE_VARIANT_ADD(audit_exec, subdomains_off) { 366 + /* clang-format on */ 367 + .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, 368 + }; 369 + 370 + /* clang-format off */ 371 + FIXTURE_VARIANT_ADD(audit_exec, cross_exec_on) { 372 + /* clang-format on */ 373 + .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON, 374 + }; 375 + 376 + /* clang-format off */ 377 + FIXTURE_VARIANT_ADD(audit_exec, subdomains_off_and_cross_exec_on) { 378 + /* clang-format on */ 379 + .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 380 + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON, 381 + }; 382 + 383 + FIXTURE_SETUP(audit_exec) 384 + { 385 + disable_caps(_metadata); 386 + set_cap(_metadata, CAP_AUDIT_CONTROL); 387 + 388 + self->audit_fd = audit_init(); 389 + EXPECT_LE(0, self->audit_fd) 390 + { 391 + const char *error_msg; 392 + 393 + /* kill "$(auditctl -s | sed -ne 's/^pid \([0-9]\+\)$/\1/p')" */ 394 + if (self->audit_fd == -EEXIST) 395 + error_msg = "socket already in use (e.g. auditd)"; 396 + else 397 + error_msg = strerror(-self->audit_fd); 398 + TH_LOG("Failed to initialize audit: %s", error_msg); 399 + } 400 + 401 + /* Applies test filter for the bin_wait_pipe_sandbox program. */ 402 + EXPECT_EQ(0, audit_init_filter_exe(&self->audit_filter, 403 + bin_wait_pipe_sandbox)); 404 + EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter, 405 + AUDIT_ADD_RULE)); 406 + 407 + clear_cap(_metadata, CAP_AUDIT_CONTROL); 408 + } 409 + 410 + FIXTURE_TEARDOWN(audit_exec) 411 + { 412 + set_cap(_metadata, CAP_AUDIT_CONTROL); 413 + EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter, 414 + AUDIT_DEL_RULE)); 415 + clear_cap(_metadata, CAP_AUDIT_CONTROL); 416 + EXPECT_EQ(0, close(self->audit_fd)); 417 + } 418 + 419 + TEST_F(audit_exec, signal_and_open) 420 + { 421 + struct audit_records records; 422 + int pipe_child[2], pipe_parent[2]; 423 + char buf_parent; 424 + pid_t child; 425 + int status; 426 + 427 + ASSERT_EQ(0, pipe2(pipe_child, 0)); 428 + ASSERT_EQ(0, pipe2(pipe_parent, 0)); 429 + 430 + child = fork(); 431 + ASSERT_LE(0, child); 432 + if (child == 0) { 433 + const struct landlock_ruleset_attr layer1 = { 434 + .scoped = LANDLOCK_SCOPE_SIGNAL, 435 + }; 436 + char pipe_child_str[12], pipe_parent_str[12]; 437 + char *const argv[] = { (char *)bin_wait_pipe_sandbox, 438 + pipe_child_str, pipe_parent_str, NULL }; 439 + int ruleset_fd; 440 + 441 + /* Passes the pipe FDs to the executed binary. */ 442 + EXPECT_EQ(0, close(pipe_child[0])); 443 + EXPECT_EQ(0, close(pipe_parent[1])); 444 + snprintf(pipe_child_str, sizeof(pipe_child_str), "%d", 445 + pipe_child[1]); 446 + snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d", 447 + pipe_parent[0]); 448 + 449 + ruleset_fd = 450 + landlock_create_ruleset(&layer1, sizeof(layer1), 0); 451 + if (ruleset_fd < 0) { 452 + perror("Failed to create a ruleset"); 453 + _exit(1); 454 + } 455 + prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 456 + if (landlock_restrict_self(ruleset_fd, 457 + variant->restrict_flags)) { 458 + perror("Failed to restrict self"); 459 + _exit(1); 460 + } 461 + close(ruleset_fd); 462 + 463 + ASSERT_EQ(0, execve(argv[0], argv, NULL)) 464 + { 465 + TH_LOG("Failed to execute \"%s\": %s", argv[0], 466 + strerror(errno)); 467 + }; 468 + _exit(1); 469 + return; 470 + } 471 + 472 + EXPECT_EQ(0, close(pipe_child[1])); 473 + EXPECT_EQ(0, close(pipe_parent[0])); 474 + 475 + /* Waits for the child. */ 476 + EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 477 + 478 + /* Tests that there was no denial until now. */ 479 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 480 + EXPECT_EQ(0, records.access); 481 + EXPECT_EQ(0, records.domain); 482 + 483 + /* 484 + * Wait for the child to do a first denied action by layer1 and 485 + * sandbox itself with layer2. 486 + */ 487 + EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); 488 + EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 489 + 490 + /* Tests that the audit record only matches the child. */ 491 + if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON) { 492 + /* Matches the current domain. */ 493 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 494 + getpid(), NULL)); 495 + } 496 + 497 + /* Checks that we didn't miss anything. */ 498 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 499 + EXPECT_EQ(0, records.access); 500 + 501 + /* 502 + * Wait for the child to do a second denied action by layer1 and 503 + * layer2, and sandbox itself with layer3. 504 + */ 505 + EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); 506 + EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 507 + 508 + /* Tests that the audit record only matches the child. */ 509 + if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON) { 510 + /* Matches the current domain. */ 511 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 512 + getpid(), NULL)); 513 + } 514 + 515 + if (!(variant->restrict_flags & 516 + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { 517 + /* Matches the child domain. */ 518 + EXPECT_EQ(0, matches_log_fs_read_root(self->audit_fd)); 519 + } 520 + 521 + /* Checks that we didn't miss anything. */ 522 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 523 + EXPECT_EQ(0, records.access); 524 + 525 + /* Waits for the child to terminate. */ 526 + EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); 527 + ASSERT_EQ(child, waitpid(child, &status, 0)); 528 + ASSERT_EQ(1, WIFEXITED(status)); 529 + ASSERT_EQ(0, WEXITSTATUS(status)); 530 + 531 + /* Tests that the audit record only matches the child. */ 532 + if (!(variant->restrict_flags & 533 + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { 534 + /* 535 + * Matches the child domains, which tests that the 536 + * llcred->domain_exec bitmask is correctly updated with a new 537 + * domain. 538 + */ 539 + EXPECT_EQ(0, matches_log_fs_read_root(self->audit_fd)); 540 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 541 + getpid(), NULL)); 542 + } 543 + 544 + /* Checks that we didn't miss anything. */ 545 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 546 + EXPECT_EQ(0, records.access); 332 547 } 333 548 334 549 TEST_HARNESS_MAIN
+1
tools/testing/selftests/landlock/common.h
··· 31 31 32 32 static const char bin_sandbox_and_launch[] = "./sandbox-and-launch"; 33 33 static const char bin_wait_pipe[] = "./wait-pipe"; 34 + static const char bin_wait_pipe_sandbox[] = "./wait-pipe-sandbox"; 34 35 35 36 static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) 36 37 {
+131
tools/testing/selftests/landlock/wait-pipe-sandbox.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Write in a pipe, wait, sandbox itself, test sandboxing, and wait again. 4 + * 5 + * Used by audit_exec.flags from audit_test.c 6 + * 7 + * Copyright © 2024-2025 Microsoft Corporation 8 + */ 9 + 10 + #define _GNU_SOURCE 11 + #include <fcntl.h> 12 + #include <linux/landlock.h> 13 + #include <linux/prctl.h> 14 + #include <signal.h> 15 + #include <stdio.h> 16 + #include <stdlib.h> 17 + #include <sys/prctl.h> 18 + #include <unistd.h> 19 + 20 + #include "wrappers.h" 21 + 22 + static int sync_with(int pipe_child, int pipe_parent) 23 + { 24 + char buf; 25 + 26 + /* Signals that we are waiting. */ 27 + if (write(pipe_child, ".", 1) != 1) { 28 + perror("Failed to write to first argument"); 29 + return 1; 30 + } 31 + 32 + /* Waits for the parent do its test. */ 33 + if (read(pipe_parent, &buf, 1) != 1) { 34 + perror("Failed to write to the second argument"); 35 + return 1; 36 + } 37 + 38 + return 0; 39 + } 40 + 41 + int main(int argc, char *argv[]) 42 + { 43 + const struct landlock_ruleset_attr layer2 = { 44 + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 45 + }; 46 + const struct landlock_ruleset_attr layer3 = { 47 + .scoped = LANDLOCK_SCOPE_SIGNAL, 48 + }; 49 + int err, pipe_child, pipe_parent, ruleset_fd; 50 + 51 + /* The first argument must be the file descriptor number of a pipe. */ 52 + if (argc != 3) { 53 + fprintf(stderr, "Wrong number of arguments (not two)\n"); 54 + return 1; 55 + } 56 + 57 + pipe_child = atoi(argv[1]); 58 + pipe_parent = atoi(argv[2]); 59 + /* PR_SET_NO_NEW_PRIVS already set by parent. */ 60 + 61 + /* First step to test parent's layer1. */ 62 + err = sync_with(pipe_child, pipe_parent); 63 + if (err) 64 + return err; 65 + 66 + /* Tries to send a signal, denied by layer1. */ 67 + if (!kill(getppid(), 0)) { 68 + fprintf(stderr, "Successfully sent a signal to the parent"); 69 + return 1; 70 + } 71 + 72 + /* Second step to test parent's layer1 and our layer2. */ 73 + err = sync_with(pipe_child, pipe_parent); 74 + if (err) 75 + return err; 76 + 77 + ruleset_fd = landlock_create_ruleset(&layer2, sizeof(layer2), 0); 78 + if (ruleset_fd < 0) { 79 + perror("Failed to create the layer2 ruleset"); 80 + return 1; 81 + } 82 + 83 + if (landlock_restrict_self(ruleset_fd, 0)) { 84 + perror("Failed to restrict self"); 85 + return 1; 86 + } 87 + close(ruleset_fd); 88 + 89 + /* Tries to send a signal, denied by layer1. */ 90 + if (!kill(getppid(), 0)) { 91 + fprintf(stderr, "Successfully sent a signal to the parent"); 92 + return 1; 93 + } 94 + 95 + /* Tries to open ., denied by layer2. */ 96 + if (open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC) >= 0) { 97 + fprintf(stderr, "Successfully opened /"); 98 + return 1; 99 + } 100 + 101 + /* Third step to test our layer2 and layer3. */ 102 + err = sync_with(pipe_child, pipe_parent); 103 + if (err) 104 + return err; 105 + 106 + ruleset_fd = landlock_create_ruleset(&layer3, sizeof(layer3), 0); 107 + if (ruleset_fd < 0) { 108 + perror("Failed to create the layer3 ruleset"); 109 + return 1; 110 + } 111 + 112 + if (landlock_restrict_self(ruleset_fd, 0)) { 113 + perror("Failed to restrict self"); 114 + return 1; 115 + } 116 + close(ruleset_fd); 117 + 118 + /* Tries to open ., denied by layer2. */ 119 + if (open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC) >= 0) { 120 + fprintf(stderr, "Successfully opened /"); 121 + return 1; 122 + } 123 + 124 + /* Tries to send a signal, denied by layer3. */ 125 + if (!kill(getppid(), 0)) { 126 + fprintf(stderr, "Successfully sent a signal to the parent"); 127 + return 1; 128 + } 129 + 130 + return 0; 131 + }