Skip to content

Commit f34e9ce

Browse files
tahifahimil0kod
authored andcommitted
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]>
1 parent c899496 commit f34e9ce

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

tools/testing/selftests/landlock/scoped_signal_test.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,188 @@ TEST(signal_scoping_threads)
297297
EXPECT_EQ(0, close(thread_pipe[1]));
298298
}
299299

300+
const short backlog = 10;
301+
302+
static volatile sig_atomic_t signal_received;
303+
304+
static void handle_sigurg(int sig)
305+
{
306+
if (sig == SIGURG)
307+
signal_received = 1;
308+
else
309+
signal_received = -1;
310+
}
311+
312+
static 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 */
326+
FIXTURE(fown) {};
327+
/* clang-format on */
328+
329+
enum fown_sandbox {
330+
SANDBOX_NONE,
331+
SANDBOX_BEFORE_FORK,
332+
SANDBOX_BEFORE_SETOWN,
333+
SANDBOX_AFTER_SETOWN,
334+
};
335+
336+
FIXTURE_VARIANT(fown)
337+
{
338+
const enum fown_sandbox sandbox_setown;
339+
};
340+
341+
/* clang-format off */
342+
FIXTURE_VARIANT_ADD(fown, no_sandbox) {
343+
/* clang-format on */
344+
.sandbox_setown = SANDBOX_NONE,
345+
};
346+
347+
/* clang-format off */
348+
FIXTURE_VARIANT_ADD(fown, sandbox_before_fork) {
349+
/* clang-format on */
350+
.sandbox_setown = SANDBOX_BEFORE_FORK,
351+
};
352+
353+
/* clang-format off */
354+
FIXTURE_VARIANT_ADD(fown, sandbox_before_setown) {
355+
/* clang-format on */
356+
.sandbox_setown = SANDBOX_BEFORE_SETOWN,
357+
};
358+
359+
/* clang-format off */
360+
FIXTURE_VARIANT_ADD(fown, sandbox_after_setown) {
361+
/* clang-format on */
362+
.sandbox_setown = SANDBOX_AFTER_SETOWN,
363+
};
364+
365+
FIXTURE_SETUP(fown)
366+
{
367+
drop_caps(_metadata);
368+
}
369+
370+
FIXTURE_TEARDOWN(fown)
371+
{
372+
}
373+
374+
/*
375+
* Sending an out of bound message will trigger the SIGURG signal
376+
* through file_send_sigiotask.
377+
*/
378+
TEST_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, sandbox
416+
* 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) was
434+
* called before any sandboxing or if the signal
435+
* 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 is
460+
* 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+
300484
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)