brintos

brintos / linux-shallow public Read only

0
0
Text · 11.0 KiB · 475ee62 Raw
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