]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
selftests/landlock: Test signal created by out-of-bound message
authorTahera Fahimi <fahimitahera@gmail.com>
Fri, 6 Sep 2024 21:30:06 +0000 (15:30 -0600)
committerMickaël Salaün <mic@digikod.net>
Mon, 16 Sep 2024 21:50:54 +0000 (23:50 +0200)
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 <fahimitahera@gmail.com>
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 <mic@digikod.net>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
tools/testing/selftests/landlock/scoped_signal_test.c

index 4426b8a8b7184b8c4cef617d472eb904ddfc739d..475ee62a832d6d656fbc7bf4f9a52ad5efd96e06 100644 (file)
@@ -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