485 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Landlock tests - Signal Scoping4 *5 * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>6 */7 8#define _GNU_SOURCE9#include <errno.h>10#include <fcntl.h>11#include <linux/landlock.h>12#include <pthread.h>13#include <signal.h>14#include <sys/prctl.h>15#include <sys/types.h>16#include <sys/wait.h>17#include <unistd.h>18 19#include "common.h"20#include "scoped_common.h"21 22/* This variable is used for handling several signals. */23static volatile sig_atomic_t is_signaled;24 25/* clang-format off */26FIXTURE(scoping_signals) {};27/* clang-format on */28 29FIXTURE_VARIANT(scoping_signals)30{31 int sig;32};33 34/* clang-format off */35FIXTURE_VARIANT_ADD(scoping_signals, sigtrap) {36 /* clang-format on */37 .sig = SIGTRAP,38};39 40/* clang-format off */41FIXTURE_VARIANT_ADD(scoping_signals, sigurg) {42 /* clang-format on */43 .sig = SIGURG,44};45 46/* clang-format off */47FIXTURE_VARIANT_ADD(scoping_signals, sighup) {48 /* clang-format on */49 .sig = SIGHUP,50};51 52/* clang-format off */53FIXTURE_VARIANT_ADD(scoping_signals, sigtstp) {54 /* clang-format on */55 .sig = SIGTSTP,56};57 58FIXTURE_SETUP(scoping_signals)59{60 drop_caps(_metadata);61 62 is_signaled = 0;63}64 65FIXTURE_TEARDOWN(scoping_signals)66{67}68 69static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext)70{71 if (sig == SIGTRAP || sig == SIGURG || sig == SIGHUP || sig == SIGTSTP)72 is_signaled = 1;73}74 75/*76 * In this test, a child process sends a signal to parent before and77 * after getting scoped.78 */79TEST_F(scoping_signals, send_sig_to_parent)80{81 int pipe_parent[2];82 int status;83 pid_t child;84 pid_t parent = getpid();85 struct sigaction action = {86 .sa_sigaction = scope_signal_handler,87 .sa_flags = SA_SIGINFO,88 89 };90 91 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));92 ASSERT_LE(0, sigaction(variant->sig, &action, NULL));93 94 /* The process should not have already been signaled. */95 EXPECT_EQ(0, is_signaled);96 97 child = fork();98 ASSERT_LE(0, child);99 if (child == 0) {100 char buf_child;101 int err;102 103 EXPECT_EQ(0, close(pipe_parent[1]));104 105 /*106 * The child process can send signal to parent when107 * domain is not scoped.108 */109 err = kill(parent, variant->sig);110 ASSERT_EQ(0, err);111 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));112 EXPECT_EQ(0, close(pipe_parent[0]));113 114 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);115 116 /*117 * The child process cannot send signal to the parent118 * anymore.119 */120 err = kill(parent, variant->sig);121 ASSERT_EQ(-1, err);122 ASSERT_EQ(EPERM, errno);123 124 /*125 * No matter of the domain, a process should be able to126 * send a signal to itself.127 */128 ASSERT_EQ(0, is_signaled);129 ASSERT_EQ(0, raise(variant->sig));130 ASSERT_EQ(1, is_signaled);131 132 _exit(_metadata->exit_code);133 return;134 }135 EXPECT_EQ(0, close(pipe_parent[0]));136 137 /* Waits for a first signal to be received, without race condition. */138 while (!is_signaled && !usleep(1))139 ;140 ASSERT_EQ(1, is_signaled);141 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));142 EXPECT_EQ(0, close(pipe_parent[1]));143 is_signaled = 0;144 145 ASSERT_EQ(child, waitpid(child, &status, 0));146 if (WIFSIGNALED(status) || !WIFEXITED(status) ||147 WEXITSTATUS(status) != EXIT_SUCCESS)148 _metadata->exit_code = KSFT_FAIL;149 150 EXPECT_EQ(0, is_signaled);151}152 153/* clang-format off */154FIXTURE(scoped_domains) {};155/* clang-format on */156 157#include "scoped_base_variants.h"158 159FIXTURE_SETUP(scoped_domains)160{161 drop_caps(_metadata);162}163 164FIXTURE_TEARDOWN(scoped_domains)165{166}167 168/*169 * This test ensures that a scoped process cannot send signal out of170 * scoped domain.171 */172TEST_F(scoped_domains, check_access_signal)173{174 pid_t child;175 pid_t parent = getpid();176 int status;177 bool can_signal_child, can_signal_parent;178 int pipe_parent[2], pipe_child[2];179 char buf_parent;180 int err;181 182 can_signal_parent = !variant->domain_child;183 can_signal_child = !variant->domain_parent;184 185 if (variant->domain_both)186 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);187 188 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));189 ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));190 191 child = fork();192 ASSERT_LE(0, child);193 if (child == 0) {194 char buf_child;195 196 EXPECT_EQ(0, close(pipe_child[0]));197 EXPECT_EQ(0, close(pipe_parent[1]));198 199 if (variant->domain_child)200 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);201 202 ASSERT_EQ(1, write(pipe_child[1], ".", 1));203 EXPECT_EQ(0, close(pipe_child[1]));204 205 /* Waits for the parent to send signals. */206 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));207 EXPECT_EQ(0, close(pipe_parent[0]));208 209 err = kill(parent, 0);210 if (can_signal_parent) {211 ASSERT_EQ(0, err);212 } else {213 ASSERT_EQ(-1, err);214 ASSERT_EQ(EPERM, errno);215 }216 /*217 * No matter of the domain, a process should be able to218 * send a signal to itself.219 */220 ASSERT_EQ(0, raise(0));221 222 _exit(_metadata->exit_code);223 return;224 }225 EXPECT_EQ(0, close(pipe_parent[0]));226 EXPECT_EQ(0, close(pipe_child[1]));227 228 if (variant->domain_parent)229 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);230 231 ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));232 EXPECT_EQ(0, close(pipe_child[0]));233 234 err = kill(child, 0);235 if (can_signal_child) {236 ASSERT_EQ(0, err);237 } else {238 ASSERT_EQ(-1, err);239 ASSERT_EQ(EPERM, errno);240 }241 ASSERT_EQ(0, raise(0));242 243 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));244 EXPECT_EQ(0, close(pipe_parent[1]));245 ASSERT_EQ(child, waitpid(child, &status, 0));246 247 if (WIFSIGNALED(status) || !WIFEXITED(status) ||248 WEXITSTATUS(status) != EXIT_SUCCESS)249 _metadata->exit_code = KSFT_FAIL;250}251 252static int thread_pipe[2];253 254enum thread_return {255 THREAD_INVALID = 0,256 THREAD_SUCCESS = 1,257 THREAD_ERROR = 2,258};259 260void *thread_func(void *arg)261{262 char buf;263 264 if (read(thread_pipe[0], &buf, 1) != 1)265 return (void *)THREAD_ERROR;266 267 return (void *)THREAD_SUCCESS;268}269 270TEST(signal_scoping_threads)271{272 pthread_t no_sandbox_thread, scoped_thread;273 enum thread_return ret = THREAD_INVALID;274 275 drop_caps(_metadata);276 ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC));277 278 ASSERT_EQ(0,279 pthread_create(&no_sandbox_thread, NULL, thread_func, NULL));280 281 /* Restricts the domain after creating the first thread. */282 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);283 284 ASSERT_EQ(EPERM, pthread_kill(no_sandbox_thread, 0));285 ASSERT_EQ(1, write(thread_pipe[1], ".", 1));286 287 ASSERT_EQ(0, pthread_create(&scoped_thread, NULL, thread_func, NULL));288 ASSERT_EQ(0, pthread_kill(scoped_thread, 0));289 ASSERT_EQ(1, write(thread_pipe[1], ".", 1));290 291 EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));292 EXPECT_EQ(THREAD_SUCCESS, ret);293 EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret));294 EXPECT_EQ(THREAD_SUCCESS, ret);295 296 EXPECT_EQ(0, close(thread_pipe[0]));297 EXPECT_EQ(0, close(thread_pipe[1]));298}299 300const short backlog = 10;301 302static volatile sig_atomic_t signal_received;303 304static void handle_sigurg(int sig)305{306 if (sig == SIGURG)307 signal_received = 1;308 else309 signal_received = -1;310}311 312static int setup_signal_handler(int signal)313{314 struct sigaction sa = {315 .sa_handler = handle_sigurg,316 };317 318 if (sigemptyset(&sa.sa_mask))319 return -1;320 321 sa.sa_flags = SA_SIGINFO | SA_RESTART;322 return sigaction(SIGURG, &sa, NULL);323}324 325/* clang-format off */326FIXTURE(fown) {};327/* clang-format on */328 329enum fown_sandbox {330 SANDBOX_NONE,331 SANDBOX_BEFORE_FORK,332 SANDBOX_BEFORE_SETOWN,333 SANDBOX_AFTER_SETOWN,334};335 336FIXTURE_VARIANT(fown)337{338 const enum fown_sandbox sandbox_setown;339};340 341/* clang-format off */342FIXTURE_VARIANT_ADD(fown, no_sandbox) {343 /* clang-format on */344 .sandbox_setown = SANDBOX_NONE,345};346 347/* clang-format off */348FIXTURE_VARIANT_ADD(fown, sandbox_before_fork) {349 /* clang-format on */350 .sandbox_setown = SANDBOX_BEFORE_FORK,351};352 353/* clang-format off */354FIXTURE_VARIANT_ADD(fown, sandbox_before_setown) {355 /* clang-format on */356 .sandbox_setown = SANDBOX_BEFORE_SETOWN,357};358 359/* clang-format off */360FIXTURE_VARIANT_ADD(fown, sandbox_after_setown) {361 /* clang-format on */362 .sandbox_setown = SANDBOX_AFTER_SETOWN,363};364 365FIXTURE_SETUP(fown)366{367 drop_caps(_metadata);368}369 370FIXTURE_TEARDOWN(fown)371{372}373 374/*375 * Sending an out of bound message will trigger the SIGURG signal376 * through file_send_sigiotask.377 */378TEST_F(fown, sigurg_socket)379{380 int server_socket, recv_socket;381 struct service_fixture server_address;382 char buffer_parent;383 int status;384 int pipe_parent[2], pipe_child[2];385 pid_t child;386 387 memset(&server_address, 0, sizeof(server_address));388 set_unix_address(&server_address, 0);389 390 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));391 ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));392 393 if (variant->sandbox_setown == SANDBOX_BEFORE_FORK)394 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);395 396 child = fork();397 ASSERT_LE(0, child);398 if (child == 0) {399 int client_socket;400 char buffer_child;401 402 EXPECT_EQ(0, close(pipe_parent[1]));403 EXPECT_EQ(0, close(pipe_child[0]));404 405 ASSERT_EQ(0, setup_signal_handler(SIGURG));406 client_socket = socket(AF_UNIX, SOCK_STREAM, 0);407 ASSERT_LE(0, client_socket);408 409 /* Waits for the parent to listen. */410 ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));411 ASSERT_EQ(0, connect(client_socket, &server_address.unix_addr,412 server_address.unix_addr_len));413 414 /*415 * Waits for the parent to accept the connection, sandbox416 * itself, and call fcntl(2).417 */418 ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));419 /* May signal itself. */420 ASSERT_EQ(1, send(client_socket, ".", 1, MSG_OOB));421 EXPECT_EQ(0, close(client_socket));422 ASSERT_EQ(1, write(pipe_child[1], ".", 1));423 EXPECT_EQ(0, close(pipe_child[1]));424 425 /* Waits for the message to be received. */426 ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));427 EXPECT_EQ(0, close(pipe_parent[0]));428 429 if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN) {430 ASSERT_EQ(0, signal_received);431 } else {432 /*433 * A signal is only received if fcntl(F_SETOWN) was434 * called before any sandboxing or if the signal435 * receiver is in the same domain.436 */437 ASSERT_EQ(1, signal_received);438 }439 _exit(_metadata->exit_code);440 return;441 }442 EXPECT_EQ(0, close(pipe_parent[0]));443 EXPECT_EQ(0, close(pipe_child[1]));444 445 server_socket = socket(AF_UNIX, SOCK_STREAM, 0);446 ASSERT_LE(0, server_socket);447 ASSERT_EQ(0, bind(server_socket, &server_address.unix_addr,448 server_address.unix_addr_len));449 ASSERT_EQ(0, listen(server_socket, backlog));450 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));451 452 recv_socket = accept(server_socket, NULL, NULL);453 ASSERT_LE(0, recv_socket);454 455 if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN)456 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);457 458 /*459 * Sets the child to receive SIGURG for MSG_OOB. This uncommon use is460 * a valid attack scenario which also simplifies this test.461 */462 ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child));463 464 if (variant->sandbox_setown == SANDBOX_AFTER_SETOWN)465 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);466 467 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));468 469 /* Waits for the child to send MSG_OOB. */470 ASSERT_EQ(1, read(pipe_child[0], &buffer_parent, 1));471 EXPECT_EQ(0, close(pipe_child[0]));472 ASSERT_EQ(1, recv(recv_socket, &buffer_parent, 1, MSG_OOB));473 EXPECT_EQ(0, close(recv_socket));474 EXPECT_EQ(0, close(server_socket));475 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));476 EXPECT_EQ(0, close(pipe_parent[1]));477 478 ASSERT_EQ(child, waitpid(child, &status, 0));479 if (WIFSIGNALED(status) || !WIFEXITED(status) ||480 WEXITSTATUS(status) != EXIT_SUCCESS)481 _metadata->exit_code = KSFT_FAIL;482}483 484TEST_HARNESS_MAIN485