Unverified Commit f34e9ce5 authored by Tahera Fahimi's avatar Tahera Fahimi Committed by Mickaël Salaün
Browse files

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: default avatarTahera 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: default avatarMickaël Salaün <mic@digikod.net>
Signed-off-by: default avatarMickaël Salaün <mic@digikod.net>
parent c8994965
Loading
Loading
Loading
Loading
+184 −0
Original line number Diff line number Diff line
@@ -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