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