[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <97fcd177f5c83b3ac88074cb9d52cb1ce684bbed.1724125513.git.fahimitahera@gmail.com>
Date: Mon, 19 Aug 2024 22:08:54 -0600
From: Tahera Fahimi <fahimitahera@...il.com>
To: outreachy@...ts.linux.dev
Cc: mic@...ikod.net,
gnoack@...gle.com,
paul@...l-moore.com,
jmorris@...ei.org,
serge@...lyn.com,
linux-security-module@...r.kernel.org,
linux-kernel@...r.kernel.org,
bjorn3_gh@...tonmail.com,
jannh@...gle.com,
netdev@...r.kernel.org,
Tahera Fahimi <fahimitahera@...il.com>
Subject: [PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests
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 <fahimitahera@...il.com>
---
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
Powered by blists - more mailing lists