[PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests
From: Tahera Fahimi <hidden>
Date: 2024-08-20 04:09:13
Also in:
lkml, netdev
Subsystem:
kernel selftest framework, landlock security module, the rest · Maintainers:
Shuah Khan, Mickaël Salaün, Linus Torvalds
This patch expands abstract UNIX socket restriction tests by testing pathname sockets connection with scoped domain. pathname_address_sockets ensures that UNIX sockets bound to a filesystem path name can still connect to a socket outside of their scoped domain. This means that even if the domain is scoped with LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, the socket can connect to a socket outside the scoped domain. Signed-off-by: Tahera Fahimi <redacted> --- changes in versions: v10: - Code improvements by changing fixture variables to local ones. - Commit improvement. v9: - Moving remove_path() back to fs_test.c, and using unlink(2) and rmdir(2) instead. - Removing hard-coded numbers and using "backlog" instead. V8: - Adding pathname_address_sockets to cover all types of address formats for unix sockets, and moving remove_path() to common.h to reuse in this test. --- .../landlock/scoped_abstract_unix_test.c | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+)
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 65c1ac2895a9..401e0d2e7025 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c@@ -928,4 +928,203 @@ TEST_F(outside_socket, socket_with_different_domain) _metadata->exit_code = KSFT_FAIL; } +static const char path1[] = TMP_DIR "/s1_variant1"; +static const char path2[] = TMP_DIR "/s2_variant1"; + +/* clang-format off */ +FIXTURE(pathname_address_sockets) {}; +/* clang-format on */ + +FIXTURE_VARIANT(pathname_address_sockets) +{ + const int domain; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_scoped_domain) { + /* clang-format on */ + .domain = SCOPE_SANDBOX, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_other_domain) { + /* clang-format on */ + .domain = OTHER_SANDBOX, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_no_domain) { + /* clang-format on */ + .domain = NO_SANDBOX, +}; + +FIXTURE_SETUP(pathname_address_sockets) +{ + disable_caps(_metadata); + umask(0077); + ASSERT_EQ(0, mkdir(TMP_DIR, 0700)); + + ASSERT_EQ(0, mknod(path1, S_IFREG | 0700, 0)) + { + TH_LOG("Failed to create file \"%s\": %s", path1, + strerror(errno)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); + } + ASSERT_EQ(0, mknod(path2, S_IFREG | 0700, 0)) + { + TH_LOG("Failed to create file \"%s\": %s", path2, + strerror(errno)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); + } +} + +FIXTURE_TEARDOWN(pathname_address_sockets) +{ + ASSERT_EQ(0, unlink(path1) & rmdir(path1)); + ASSERT_EQ(0, unlink(path2) & rmdir(path2)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); +} + +TEST_F(pathname_address_sockets, scoped_pathname_sockets) +{ + struct service_fixture stream_address, dgram_address; + const char *const stream_path = path1; + const char *const dgram_path = path2; + socklen_t size, size_dg; + struct sockaddr_un srv_un, srv_un_dg; + int pipe_parent[2]; + pid_t child; + int status; + char buf_child; + int socket_fds_stream[2]; + + /* setup abstract addresses */ + memset(&stream_address, 0, sizeof(stream_address)); + set_unix_address(&stream_address, 0); + memset(&dgram_address, 0, sizeof(dgram_address)); + set_unix_address(&dgram_address, 0); + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, + socket_fds_stream)); + + srv_un.sun_family = AF_UNIX; + snprintf(srv_un.sun_path, sizeof(srv_un.sun_path), "%s", stream_path); + size = offsetof(struct sockaddr_un, sun_path) + strlen(srv_un.sun_path); + + srv_un_dg.sun_family = AF_UNIX; + snprintf(srv_un_dg.sun_path, sizeof(srv_un_dg.sun_path), "%s", + dgram_path); + size_dg = offsetof(struct sockaddr_un, sun_path) + + strlen(srv_un_dg.sun_path); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int cli_fd, cli_fd_dg; + int err, err_dg; + int client, dgram_client; + int sample = socket(AF_UNIX, SOCK_STREAM, 0); + + ASSERT_LE(0, sample); + ASSERT_EQ(0, close(pipe_parent[1])); + + /* scope the domain */ + if (variant->domain == SCOPE_SANDBOX) + create_unix_domain(_metadata); + else if (variant->domain == OTHER_SANDBOX) + create_fs_domain(_metadata); + + ASSERT_EQ(0, close(socket_fds_stream[1])); + ASSERT_EQ(0, send_fd(socket_fds_stream[0], sample)); + ASSERT_EQ(0, close(sample)); + ASSERT_EQ(0, close(socket_fds_stream[0])); + + /* wait for server to listen */ + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + + /* connect with pathname sockets */ + cli_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, cli_fd); + ASSERT_EQ(0, connect(cli_fd, &srv_un, size)); + ASSERT_EQ(0, close(cli_fd)); + + cli_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_LE(0, cli_fd_dg); + ASSERT_EQ(0, connect(cli_fd_dg, &srv_un_dg, size_dg)); + + ASSERT_EQ(0, close(cli_fd_dg)); + + /* check connection with abstract sockets */ + client = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0); + + ASSERT_NE(-1, client); + ASSERT_NE(-1, dgram_client); + + err = connect(client, &stream_address.unix_addr, + stream_address.unix_addr_len); + err_dg = connect(dgram_client, &dgram_address.unix_addr, + dgram_address.unix_addr_len); + if (variant->domain == SCOPE_SANDBOX) { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, err_dg); + EXPECT_EQ(EPERM, errno); + } else { + EXPECT_EQ(0, err); + EXPECT_EQ(0, err_dg); + } + ASSERT_EQ(0, close(client)); + ASSERT_EQ(0, close(dgram_client)); + + _exit(_metadata->exit_code); + return; + } + int srv_fd, srv_fd_dg, server, dgram_server; + int recv_data; + + ASSERT_EQ(0, close(pipe_parent[0])); + + recv_data = recv_fd(socket_fds_stream[1]); + ASSERT_LE(0, recv_data); + ASSERT_LE(0, close(socket_fds_stream[1])); + + /* Sets up a server */ + srv_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, srv_fd); + ASSERT_EQ(0, unlink(stream_path)); + ASSERT_EQ(0, bind(srv_fd, &srv_un, size)); + ASSERT_EQ(0, listen(srv_fd, backlog)); + + /* set up a datagram server */ + ASSERT_EQ(0, unlink(dgram_path)); + srv_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_LE(0, srv_fd_dg); + ASSERT_EQ(0, bind(srv_fd_dg, (struct sockaddr *)&srv_un_dg, size_dg)); + + /*set up abstract servers */ + server = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, server); + ASSERT_NE(-1, dgram_server); + ASSERT_EQ(0, bind(server, &stream_address.unix_addr, + stream_address.unix_addr_len)); + ASSERT_EQ(0, bind(dgram_server, &dgram_address.unix_addr, + dgram_address.unix_addr_len)); + ASSERT_EQ(0, listen(server, backlog)); + + /* servers are listening, signal to child */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(0, close(srv_fd)); + ASSERT_EQ(0, close(srv_fd_dg)); + ASSERT_EQ(0, close(server)); + ASSERT_EQ(0, close(dgram_server)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + TEST_HARNESS_MAIN
--
2.34.1