diff options
author | Tahera Fahimi <[email protected]> | 2024-09-06 15:30:06 -0600 |
---|---|---|
committer | Mickaël Salaün <[email protected]> | 2024-09-16 23:50:54 +0200 |
commit | f34e9ce5f4794387121120b2d2ff5aa265ef6ce9 (patch) | |
tree | fe664091ed33f83683953c909788a777577b4335 | |
parent | c8994965013ee62cfb0f3cec01aa447f9077a869 (diff) |
selftests/landlock: Test signal created by out-of-bound message
Add a test to verify that the SIGURG signal created by an out-of-bound
message in UNIX sockets is well controlled by the file_send_sigiotask
hook.
Test coverage for security/landlock is 92.2% of 1046 lines according to
gcc/gcov-14.
Signed-off-by: Tahera Fahimi <[email protected]>
Link: https://lore.kernel.org/r/50daeed4d4f60d71e9564d0f24004a373fc5f7d5.1725657728.git.fahimitahera@gmail.com
[mic: Improve commit message and add test coverage, improve test with
four variants to fully cover the hook, use abstract unix socket to avoid
managing a file, use dedicated variable per process, add comments, avoid
negative ASSERT, move close calls]
Co-developed-by: Mickaël Salaün <[email protected]>
Signed-off-by: Mickaël Salaün <[email protected]>
-rw-r--r-- | tools/testing/selftests/landlock/scoped_signal_test.c | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c index 4426b8a8b718..475ee62a832d 100644 --- a/tools/testing/selftests/landlock/scoped_signal_test.c +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -297,4 +297,188 @@ TEST(signal_scoping_threads) EXPECT_EQ(0, close(thread_pipe[1])); } +const short backlog = 10; + +static volatile sig_atomic_t signal_received; + +static void handle_sigurg(int sig) +{ + if (sig == SIGURG) + signal_received = 1; + else + signal_received = -1; +} + +static int setup_signal_handler(int signal) +{ + struct sigaction sa = { + .sa_handler = handle_sigurg, + }; + + if (sigemptyset(&sa.sa_mask)) + return -1; + + sa.sa_flags = SA_SIGINFO | SA_RESTART; + return sigaction(SIGURG, &sa, NULL); +} + +/* clang-format off */ +FIXTURE(fown) {}; +/* clang-format on */ + +enum fown_sandbox { + SANDBOX_NONE, + SANDBOX_BEFORE_FORK, + SANDBOX_BEFORE_SETOWN, + SANDBOX_AFTER_SETOWN, +}; + +FIXTURE_VARIANT(fown) +{ + const enum fown_sandbox sandbox_setown; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(fown, no_sandbox) { + /* clang-format on */ + .sandbox_setown = SANDBOX_NONE, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(fown, sandbox_before_fork) { + /* clang-format on */ + .sandbox_setown = SANDBOX_BEFORE_FORK, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(fown, sandbox_before_setown) { + /* clang-format on */ + .sandbox_setown = SANDBOX_BEFORE_SETOWN, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(fown, sandbox_after_setown) { + /* clang-format on */ + .sandbox_setown = SANDBOX_AFTER_SETOWN, +}; + +FIXTURE_SETUP(fown) +{ + drop_caps(_metadata); +} + +FIXTURE_TEARDOWN(fown) +{ +} + +/* + * Sending an out of bound message will trigger the SIGURG signal + * through file_send_sigiotask. + */ +TEST_F(fown, sigurg_socket) +{ + int server_socket, recv_socket; + struct service_fixture server_address; + char buffer_parent; + int status; + int pipe_parent[2], pipe_child[2]; + pid_t child; + + memset(&server_address, 0, sizeof(server_address)); + set_unix_address(&server_address, 0); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + + if (variant->sandbox_setown == SANDBOX_BEFORE_FORK) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int client_socket; + char buffer_child; + + EXPECT_EQ(0, close(pipe_parent[1])); + EXPECT_EQ(0, close(pipe_child[0])); + + ASSERT_EQ(0, setup_signal_handler(SIGURG)); + client_socket = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, client_socket); + + /* Waits for the parent to listen. */ + ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1)); + ASSERT_EQ(0, connect(client_socket, &server_address.unix_addr, + server_address.unix_addr_len)); + + /* + * Waits for the parent to accept the connection, sandbox + * itself, and call fcntl(2). + */ + ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1)); + /* May signal itself. */ + ASSERT_EQ(1, send(client_socket, ".", 1, MSG_OOB)); + EXPECT_EQ(0, close(client_socket)); + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + EXPECT_EQ(0, close(pipe_child[1])); + + /* Waits for the message to be received. */ + ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1)); + EXPECT_EQ(0, close(pipe_parent[0])); + + if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN) { + ASSERT_EQ(0, signal_received); + } else { + /* + * A signal is only received if fcntl(F_SETOWN) was + * called before any sandboxing or if the signal + * receiver is in the same domain. + */ + ASSERT_EQ(1, signal_received); + } + _exit(_metadata->exit_code); + return; + } + EXPECT_EQ(0, close(pipe_parent[0])); + EXPECT_EQ(0, close(pipe_child[1])); + + server_socket = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, server_socket); + ASSERT_EQ(0, bind(server_socket, &server_address.unix_addr, + server_address.unix_addr_len)); + ASSERT_EQ(0, listen(server_socket, backlog)); + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + recv_socket = accept(server_socket, NULL, NULL); + ASSERT_LE(0, recv_socket); + + if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + /* + * Sets the child to receive SIGURG for MSG_OOB. This uncommon use is + * a valid attack scenario which also simplifies this test. + */ + ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child)); + + if (variant->sandbox_setown == SANDBOX_AFTER_SETOWN) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + /* Waits for the child to send MSG_OOB. */ + ASSERT_EQ(1, read(pipe_child[0], &buffer_parent, 1)); + EXPECT_EQ(0, close(pipe_child[0])); + ASSERT_EQ(1, recv(recv_socket, &buffer_parent, 1, MSG_OOB)); + EXPECT_EQ(0, close(recv_socket)); + EXPECT_EQ(0, close(server_socket)); + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + EXPECT_EQ(0, close(pipe_parent[1])); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + TEST_HARNESS_MAIN |