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

selftests/landlock: Add audit tests for ptrace

Add tests for all ptrace actions checking "blockers=ptrace" records.

This also improves PTRACE_TRACEME and PTRACE_ATTACH tests by making sure
that the restrictions comes from Landlock, and with the expected
process. These extended tests are like enhanced errno checks that make
sure Landlock enforcement is consistent.

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

+140
+140
tools/testing/selftests/landlock/ptrace_test.c
··· 4 4 * 5 5 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> 6 6 * Copyright © 2019-2020 ANSSI 7 + * Copyright © 2024-2025 Microsoft Corporation 7 8 */ 8 9 9 10 #define _GNU_SOURCE ··· 18 17 #include <sys/wait.h> 19 18 #include <unistd.h> 20 19 20 + #include "audit.h" 21 21 #include "common.h" 22 22 23 23 /* Copied from security/yama/yama_lsm.c */ ··· 434 432 if (WIFSIGNALED(status) || !WIFEXITED(status) || 435 433 WEXITSTATUS(status) != EXIT_SUCCESS) 436 434 _metadata->exit_code = KSFT_FAIL; 435 + } 436 + 437 + static int matches_log_ptrace(struct __test_metadata *const _metadata, 438 + int audit_fd, const pid_t opid) 439 + { 440 + static const char log_template[] = REGEX_LANDLOCK_PREFIX 441 + " blockers=ptrace opid=%d ocomm=\"ptrace_test\"$"; 442 + char log_match[sizeof(log_template) + 10]; 443 + int log_match_len; 444 + 445 + log_match_len = 446 + snprintf(log_match, sizeof(log_match), log_template, opid); 447 + if (log_match_len > sizeof(log_match)) 448 + return -E2BIG; 449 + 450 + return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match, 451 + NULL); 452 + } 453 + 454 + FIXTURE(audit) 455 + { 456 + struct audit_filter audit_filter; 457 + int audit_fd; 458 + }; 459 + 460 + FIXTURE_SETUP(audit) 461 + { 462 + disable_caps(_metadata); 463 + set_cap(_metadata, CAP_AUDIT_CONTROL); 464 + self->audit_fd = audit_init_with_exe_filter(&self->audit_filter); 465 + EXPECT_LE(0, self->audit_fd); 466 + clear_cap(_metadata, CAP_AUDIT_CONTROL); 467 + } 468 + 469 + FIXTURE_TEARDOWN_PARENT(audit) 470 + { 471 + EXPECT_EQ(0, audit_cleanup(-1, NULL)); 472 + } 473 + 474 + /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */ 475 + TEST_F(audit, trace) 476 + { 477 + pid_t child; 478 + int status; 479 + int pipe_child[2], pipe_parent[2]; 480 + int yama_ptrace_scope; 481 + char buf_parent; 482 + struct audit_records records; 483 + 484 + /* Makes sure there is no superfluous logged records. */ 485 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 486 + EXPECT_EQ(0, records.access); 487 + EXPECT_EQ(0, records.domain); 488 + 489 + yama_ptrace_scope = get_yama_ptrace_scope(); 490 + ASSERT_LE(0, yama_ptrace_scope); 491 + 492 + if (yama_ptrace_scope > YAMA_SCOPE_DISABLED) 493 + TH_LOG("Incomplete tests due to Yama restrictions (scope %d)", 494 + yama_ptrace_scope); 495 + 496 + /* 497 + * Removes all effective and permitted capabilities to not interfere 498 + * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS. 499 + */ 500 + drop_caps(_metadata); 501 + 502 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 503 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 504 + 505 + child = fork(); 506 + ASSERT_LE(0, child); 507 + if (child == 0) { 508 + char buf_child; 509 + 510 + ASSERT_EQ(0, close(pipe_parent[1])); 511 + ASSERT_EQ(0, close(pipe_child[0])); 512 + 513 + /* Waits for the parent to be in a domain, if any. */ 514 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 515 + 516 + /* Tests child PTRACE_TRACEME. */ 517 + EXPECT_EQ(-1, ptrace(PTRACE_TRACEME)); 518 + EXPECT_EQ(EPERM, errno); 519 + /* We should see the child process. */ 520 + EXPECT_EQ(0, matches_log_ptrace(_metadata, self->audit_fd, 521 + getpid())); 522 + 523 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 524 + EXPECT_EQ(0, records.access); 525 + /* Checks for a domain creation. */ 526 + EXPECT_EQ(1, records.domain); 527 + 528 + /* 529 + * Signals that the PTRACE_ATTACH test is done and the 530 + * PTRACE_TRACEME test is ongoing. 531 + */ 532 + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); 533 + 534 + /* Waits for the parent PTRACE_ATTACH test. */ 535 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 536 + _exit(_metadata->exit_code); 537 + return; 538 + } 539 + 540 + ASSERT_EQ(0, close(pipe_child[1])); 541 + ASSERT_EQ(0, close(pipe_parent[0])); 542 + create_domain(_metadata); 543 + 544 + /* Signals that the parent is in a domain. */ 545 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 546 + 547 + /* 548 + * Waits for the child to test PTRACE_ATTACH on the parent and start 549 + * testing PTRACE_TRACEME. 550 + */ 551 + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 552 + 553 + /* The child should not be traced by the parent. */ 554 + EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0)); 555 + EXPECT_EQ(ESRCH, errno); 556 + 557 + /* Tests PTRACE_ATTACH on the child. */ 558 + EXPECT_EQ(-1, ptrace(PTRACE_ATTACH, child, NULL, 0)); 559 + EXPECT_EQ(EPERM, errno); 560 + EXPECT_EQ(0, matches_log_ptrace(_metadata, self->audit_fd, child)); 561 + 562 + /* Signals that the parent PTRACE_ATTACH test is done. */ 563 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 564 + ASSERT_EQ(child, waitpid(child, &status, 0)); 565 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 566 + WEXITSTATUS(status) != EXIT_SUCCESS) 567 + _metadata->exit_code = KSFT_FAIL; 568 + 569 + /* Makes sure there is no superfluous logged records. */ 570 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 571 + EXPECT_EQ(0, records.access); 572 + EXPECT_EQ(0, records.domain); 437 573 } 438 574 439 575 TEST_HARNESS_MAIN