* [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too.
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (3 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 4/6] selftests/landlock: Support pathname socket path in set_unix_address Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests Tingmao Wang
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
6 siblings, 0 replies; 9+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Since there is very little difference between abstract and pathname
sockets in terms of testing of the scoped access checks (the only
difference is in which scope bit control which form of socket), it makes
sense to reuse the existing test for both type of sockets. Therefore, we
rename scoped_abstract_unix_test.c to scoped_unix_test.c and extend the
scoped_domains test to test pathname (i.e. non-abstract) sockets too.
Since we can't change the variant data of scoped_domains (as it is defined
in the shared .h file), we do this by extracting the actual test code into
a function, and call it from different test cases.
Also extend scoped_audit (this time we can use variants) to test both
abstract and pathname sockets. For pathname sockets, audit_log_lsm_data
will produce path="..." (or hex if path contains control characters) with
absolute paths from the dentry, so we need to construct the escaped regex
for the real path like in fs_test.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
...bstract_unix_test.c => scoped_unix_test.c} | 256 ++++++++++++++----
1 file changed, 206 insertions(+), 50 deletions(-)
rename tools/testing/selftests/landlock/{scoped_abstract_unix_test.c => scoped_unix_test.c} (81%)
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
similarity index 81%
rename from tools/testing/selftests/landlock/scoped_abstract_unix_test.c
rename to tools/testing/selftests/landlock/scoped_unix_test.c
index 4a790e2d387d..669418c97509 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_unix_test.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Landlock tests - Abstract UNIX socket
+ * Landlock tests - Scoped access checks for UNIX socket (abstract and
+ * pathname)
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
@@ -19,6 +20,7 @@
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <stdlib.h>
#include "audit.h"
#include "common.h"
@@ -47,7 +49,8 @@ static void create_fs_domain(struct __test_metadata *const _metadata)
FIXTURE(scoped_domains)
{
- struct service_fixture stream_address, dgram_address;
+ struct service_fixture stream_address_abstract, dgram_address_abstract,
+ stream_address_pathname, dgram_address_pathname;
};
#include "scoped_base_variants.h"
@@ -56,27 +59,62 @@ FIXTURE_SETUP(scoped_domains)
{
drop_caps(_metadata);
- memset(&self->stream_address, 0, sizeof(self->stream_address));
- memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->stream_address, 0, true);
- set_unix_address(&self->dgram_address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->stream_address_abstract, 0,
+ sizeof(self->stream_address_abstract));
+ memset(&self->dgram_address_abstract, 0,
+ sizeof(self->dgram_address_abstract));
+ memset(&self->stream_address_pathname, 0,
+ sizeof(self->stream_address_pathname));
+ memset(&self->dgram_address_pathname, 0,
+ sizeof(self->dgram_address_pathname));
+ set_unix_address(&self->stream_address_abstract, 0, true);
+ set_unix_address(&self->dgram_address_abstract, 1, true);
+ set_unix_address(&self->stream_address_pathname, 0, false);
+ set_unix_address(&self->dgram_address_pathname, 1, false);
+}
+
+/* Remove @path if it exists */
+int remove_path(const char *path)
+{
+ if (unlink(path) == -1) {
+ if (errno != ENOENT)
+ return -errno;
+ }
+ return 0;
}
FIXTURE_TEARDOWN(scoped_domains)
{
+ EXPECT_EQ(0, remove_path(self->stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
* Test unix_stream_connect() and unix_may_send() for a child connecting to its
* parent, when they have scoped domain or no domain.
*/
-TEST_F(scoped_domains, connect_to_parent)
+static void test_connect_to_parent(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_domains) * self,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ const bool abstract)
{
pid_t child;
bool can_connect_to_parent;
int status;
int pipe_parent[2];
int stream_server, dgram_server;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *stream_address =
+ abstract ? &self->stream_address_abstract :
+ &self->stream_address_pathname;
+ const struct service_fixture *dgram_address =
+ abstract ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
/*
* can_connect_to_parent is true if a child process can connect to its
@@ -87,8 +125,7 @@ TEST_F(scoped_domains, connect_to_parent)
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
if (variant->domain_both) {
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (!__test_passed(_metadata))
return;
}
@@ -102,8 +139,7 @@ TEST_F(scoped_domains, connect_to_parent)
EXPECT_EQ(0, close(pipe_parent[1]));
if (variant->domain_child)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_client);
@@ -113,8 +149,8 @@ TEST_F(scoped_domains, connect_to_parent)
/* Waits for the server. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
- err = connect(stream_client, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len);
+ err = connect(stream_client, &stream_address->unix_addr,
+ stream_address->unix_addr_len);
if (can_connect_to_parent) {
EXPECT_EQ(0, err);
} else {
@@ -123,8 +159,8 @@ TEST_F(scoped_domains, connect_to_parent)
}
EXPECT_EQ(0, close(stream_client));
- err = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
if (can_connect_to_parent) {
EXPECT_EQ(0, err);
} else {
@@ -137,17 +173,16 @@ TEST_F(scoped_domains, connect_to_parent)
}
EXPECT_EQ(0, close(pipe_parent[0]));
if (variant->domain_parent)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server);
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0, bind(stream_server, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_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(stream_server, backlog));
/* Signals to child that the parent is listening. */
@@ -166,7 +201,11 @@ TEST_F(scoped_domains, connect_to_parent)
* Test unix_stream_connect() and unix_may_send() for a parent connecting to
* its child, when they have scoped domain or no domain.
*/
-TEST_F(scoped_domains, connect_to_child)
+static void test_connect_to_child(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_domains) * self,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ const bool abstract)
{
pid_t child;
bool can_connect_to_child;
@@ -174,6 +213,14 @@ TEST_F(scoped_domains, connect_to_child)
int pipe_child[2], pipe_parent[2];
char buf;
int stream_client, dgram_client;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *stream_address =
+ abstract ? &self->stream_address_abstract :
+ &self->stream_address_pathname;
+ const struct service_fixture *dgram_address =
+ abstract ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
/*
* can_connect_to_child is true if a parent process can connect to its
@@ -185,8 +232,7 @@ TEST_F(scoped_domains, connect_to_child)
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
if (variant->domain_both) {
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (!__test_passed(_metadata))
return;
}
@@ -199,8 +245,7 @@ TEST_F(scoped_domains, connect_to_child)
EXPECT_EQ(0, close(pipe_parent[1]));
EXPECT_EQ(0, close(pipe_child[0]));
if (variant->domain_child)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/* Waits for the parent to be in a domain, if any. */
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
@@ -209,11 +254,10 @@ TEST_F(scoped_domains, connect_to_child)
ASSERT_LE(0, stream_server);
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0,
- bind(stream_server, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_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(stream_server, backlog));
/* Signals to the parent that child is listening. */
@@ -230,8 +274,7 @@ TEST_F(scoped_domains, connect_to_child)
EXPECT_EQ(0, close(pipe_parent[0]));
if (variant->domain_parent)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -243,11 +286,11 @@ TEST_F(scoped_domains, connect_to_child)
/* Waits for the child to listen */
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- err_stream = connect(stream_client, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len);
+ err_stream = connect(stream_client, &stream_address->unix_addr,
+ stream_address->unix_addr_len);
errno_stream = errno;
- err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err_dgram = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
errno_dgram = errno;
if (can_connect_to_child) {
EXPECT_EQ(0, err_stream);
@@ -268,19 +311,79 @@ TEST_F(scoped_domains, connect_to_child)
_metadata->exit_code = KSFT_FAIL;
}
+/*
+ * Test unix_stream_connect() and unix_may_send() for a child connecting to its
+ * parent, when they have scoped domain or no domain.
+ */
+TEST_F(scoped_domains, abstract_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, self, variant, true);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a parent connecting to
+ * its child, when they have scoped domain or no domain.
+ */
+TEST_F(scoped_domains, abstract_connect_to_child)
+{
+ test_connect_to_child(_metadata, self, variant, true);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a child connecting to its
+ * parent with pathname sockets.
+ */
+TEST_F(scoped_domains, pathname_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, self, variant, false);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a parent connecting to
+ * its child with pathname sockets.
+ */
+TEST_F(scoped_domains, pathname_connect_to_child)
+{
+ test_connect_to_child(_metadata, self, variant, false);
+}
+
FIXTURE(scoped_audit)
{
- struct service_fixture dgram_address;
+ struct service_fixture dgram_address_abstract, dgram_address_pathname;
struct audit_filter audit_filter;
int audit_fd;
};
+FIXTURE_VARIANT(scoped_audit)
+{
+ const bool abstract_socket;
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, abstract_socket)
+{
+ // clang-format on
+ .abstract_socket = true,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, pathname_socket)
+{
+ // clang-format on
+ .abstract_socket = false,
+};
+
FIXTURE_SETUP(scoped_audit)
{
disable_caps(_metadata);
- memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->dgram_address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+ memset(&self->dgram_address_abstract, 0,
+ sizeof(self->dgram_address_abstract));
+ memset(&self->dgram_address_pathname, 0,
+ sizeof(self->dgram_address_pathname));
+ set_unix_address(&self->dgram_address_abstract, 1, true);
+ set_unix_address(&self->dgram_address_pathname, 1, false);
set_cap(_metadata, CAP_AUDIT_CONTROL);
self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
@@ -291,6 +394,8 @@ FIXTURE_SETUP(scoped_audit)
FIXTURE_TEARDOWN_PARENT(scoped_audit)
{
EXPECT_EQ(0, audit_cleanup(-1, NULL));
+ EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
@@ -308,6 +413,12 @@ TEST_F(scoped_audit, connect_to_child)
char buf;
int dgram_client;
struct audit_records records;
+ struct service_fixture *const dgram_address =
+ variant->abstract_socket ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
+ size_t log_match_remaining = 500;
+ char log_match[log_match_remaining];
+ char *log_match_cursor = log_match;
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
@@ -330,8 +441,8 @@ TEST_F(scoped_audit, connect_to_child)
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len));
/* Signals to the parent that child is listening. */
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -345,7 +456,9 @@ TEST_F(scoped_audit, connect_to_child)
EXPECT_EQ(0, close(pipe_child[1]));
EXPECT_EQ(0, close(pipe_parent[0]));
- create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata,
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -355,19 +468,62 @@ TEST_F(scoped_audit, connect_to_child)
/* Waits for the child to listen */
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err_dgram = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
EXPECT_EQ(-1, err_dgram);
EXPECT_EQ(EPERM, errno);
- EXPECT_EQ(
- 0,
- audit_match_record(
- self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+ if (variant->abstract_socket) {
+ log_match_cursor = stpncpy(
+ log_match,
REGEX_LANDLOCK_PREFIX
" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
"[0-9A-F]\\+$",
- NULL));
+ log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ } else {
+ /*
+ * It is assumed that absolute_path does not contain control
+ * characters nor spaces, see audit_string_contains_control().
+ */
+ char *absolute_path =
+ realpath(dgram_address->unix_addr.sun_path, NULL);
+
+ EXPECT_NE(NULL, absolute_path)
+ {
+ TH_LOG("realpath() failed: %s", strerror(errno));
+ return;
+ }
+
+ log_match_cursor =
+ stpncpy(log_match,
+ REGEX_LANDLOCK_PREFIX
+ " blockers=scope\\.pathname_unix_socket path=\"",
+ log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ log_match_cursor = regex_escape(absolute_path, log_match_cursor,
+ log_match_remaining);
+ free(absolute_path);
+ if (log_match_cursor < 0) {
+ TH_LOG("regex_escape() failed (buffer too small)");
+ return;
+ }
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ log_match_cursor =
+ stpncpy(log_match_cursor, "\"$", log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ }
+
+ EXPECT_EQ(0, audit_match_record(self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+ log_match, NULL));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
EXPECT_EQ(0, close(dgram_client));
--
2.52.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (4 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
6 siblings, 0 replies; 9+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
While this produces a lot of change, it does allow us to "simultaneously"
test both abstract and pathname UNIX sockets with reletively little code
duplication, since they are really similar.
Tests touched: scoped_vs_unscoped, outside_socket,
various_address_sockets, datagram_sockets, self_connect.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
.../selftests/landlock/scoped_unix_test.c | 599 ++++++++++++------
1 file changed, 395 insertions(+), 204 deletions(-)
diff --git a/tools/testing/selftests/landlock/scoped_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
index 669418c97509..6d1541f77dbe 100644
--- a/tools/testing/selftests/landlock/scoped_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_unix_test.c
@@ -536,8 +536,12 @@ TEST_F(scoped_audit, connect_to_child)
FIXTURE(scoped_vs_unscoped)
{
- struct service_fixture parent_stream_address, parent_dgram_address,
- child_stream_address, child_dgram_address;
+ struct service_fixture parent_stream_address_abstract,
+ parent_dgram_address_abstract, child_stream_address_abstract,
+ child_dgram_address_abstract;
+ struct service_fixture parent_stream_address_pathname,
+ parent_dgram_address_pathname, child_stream_address_pathname,
+ child_dgram_address_pathname;
};
#include "scoped_multiple_domain_variants.h"
@@ -546,35 +550,75 @@ FIXTURE_SETUP(scoped_vs_unscoped)
{
drop_caps(_metadata);
- memset(&self->parent_stream_address, 0,
- sizeof(self->parent_stream_address));
- set_unix_address(&self->parent_stream_address, 0, true);
- memset(&self->parent_dgram_address, 0,
- sizeof(self->parent_dgram_address));
- set_unix_address(&self->parent_dgram_address, 1, true);
- memset(&self->child_stream_address, 0,
- sizeof(self->child_stream_address));
- set_unix_address(&self->child_stream_address, 2, true);
- memset(&self->child_dgram_address, 0,
- sizeof(self->child_dgram_address));
- set_unix_address(&self->child_dgram_address, 3, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ /* Abstract addresses. */
+ memset(&self->parent_stream_address_abstract, 0,
+ sizeof(self->parent_stream_address_abstract));
+ set_unix_address(&self->parent_stream_address_abstract, 0, true);
+ memset(&self->parent_dgram_address_abstract, 0,
+ sizeof(self->parent_dgram_address_abstract));
+ set_unix_address(&self->parent_dgram_address_abstract, 1, true);
+ memset(&self->child_stream_address_abstract, 0,
+ sizeof(self->child_stream_address_abstract));
+ set_unix_address(&self->child_stream_address_abstract, 2, true);
+ memset(&self->child_dgram_address_abstract, 0,
+ sizeof(self->child_dgram_address_abstract));
+ set_unix_address(&self->child_dgram_address_abstract, 3, true);
+
+ /* Pathname addresses. */
+ memset(&self->parent_stream_address_pathname, 0,
+ sizeof(self->parent_stream_address_pathname));
+ set_unix_address(&self->parent_stream_address_pathname, 4, false);
+ memset(&self->parent_dgram_address_pathname, 0,
+ sizeof(self->parent_dgram_address_pathname));
+ set_unix_address(&self->parent_dgram_address_pathname, 5, false);
+ memset(&self->child_stream_address_pathname, 0,
+ sizeof(self->child_stream_address_pathname));
+ set_unix_address(&self->child_stream_address_pathname, 6, false);
+ memset(&self->child_dgram_address_pathname, 0,
+ sizeof(self->child_dgram_address_pathname));
+ set_unix_address(&self->child_dgram_address_pathname, 7, false);
}
FIXTURE_TEARDOWN(scoped_vs_unscoped)
{
+ EXPECT_EQ(0, remove_path(self->parent_stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->parent_dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->child_stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->child_dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
* Test unix_stream_connect and unix_may_send for parent, child and
* grand child processes when they can have scoped or non-scoped domains.
*/
-TEST_F(scoped_vs_unscoped, unix_scoping)
+static void test_scoped_vs_unscoped(
+ struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_vs_unscoped) * self,
+ const FIXTURE_VARIANT(scoped_vs_unscoped) * variant,
+ const bool abstract)
{
pid_t child;
int status;
bool can_connect_to_parent, can_connect_to_child;
int pipe_parent[2];
int stream_server_parent, dgram_server_parent;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *parent_stream_address =
+ abstract ? &self->parent_stream_address_abstract :
+ &self->parent_stream_address_pathname;
+ const struct service_fixture *parent_dgram_address =
+ abstract ? &self->parent_dgram_address_abstract :
+ &self->parent_dgram_address_pathname;
+ const struct service_fixture *child_stream_address =
+ abstract ? &self->child_stream_address_abstract :
+ &self->child_stream_address_pathname;
+ const struct service_fixture *child_dgram_address =
+ abstract ? &self->child_dgram_address_abstract :
+ &self->child_dgram_address_pathname;
can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX);
can_connect_to_parent = (can_connect_to_child &&
@@ -585,8 +629,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_all == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_all == SCOPE_SANDBOX)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
child = fork();
ASSERT_LE(0, child);
@@ -600,8 +643,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_children == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_children == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
grand_child = fork();
ASSERT_LE(0, grand_child);
@@ -616,9 +658,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_grand_child == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_grand_child == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_client);
@@ -626,15 +666,13 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_LE(0, dgram_client);
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- stream_err = connect(
- stream_client,
- &self->child_stream_address.unix_addr,
- self->child_stream_address.unix_addr_len);
+ stream_err = connect(stream_client,
+ &child_stream_address->unix_addr,
+ child_stream_address->unix_addr_len);
stream_errno = errno;
- dgram_err = connect(
- dgram_client,
- &self->child_dgram_address.unix_addr,
- self->child_dgram_address.unix_addr_len);
+ dgram_err = connect(dgram_client,
+ &child_dgram_address->unix_addr,
+ child_dgram_address->unix_addr_len);
dgram_errno = errno;
if (can_connect_to_child) {
EXPECT_EQ(0, stream_err);
@@ -653,14 +691,12 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
stream_err = connect(
- stream_client,
- &self->parent_stream_address.unix_addr,
- self->parent_stream_address.unix_addr_len);
+ stream_client, &parent_stream_address->unix_addr,
+ parent_stream_address->unix_addr_len);
stream_errno = errno;
dgram_err = connect(
- dgram_client,
- &self->parent_dgram_address.unix_addr,
- self->parent_dgram_address.unix_addr_len);
+ dgram_client, &parent_dgram_address->unix_addr,
+ parent_dgram_address->unix_addr_len);
dgram_errno = errno;
if (can_connect_to_parent) {
EXPECT_EQ(0, stream_err);
@@ -681,8 +717,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_child == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_child == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server_child);
@@ -690,11 +725,11 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_LE(0, dgram_server_child);
ASSERT_EQ(0, bind(stream_server_child,
- &self->child_stream_address.unix_addr,
- self->child_stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server_child,
- &self->child_dgram_address.unix_addr,
- self->child_dgram_address.unix_addr_len));
+ &child_stream_address->unix_addr,
+ child_stream_address->unix_addr_len));
+ ASSERT_EQ(0,
+ bind(dgram_server_child, &child_dgram_address->unix_addr,
+ child_dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server_child, backlog));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -708,19 +743,16 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_parent == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_parent == SCOPE_SANDBOX)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server_parent);
dgram_server_parent = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server_parent);
- ASSERT_EQ(0, bind(stream_server_parent,
- &self->parent_stream_address.unix_addr,
- self->parent_stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server_parent,
- &self->parent_dgram_address.unix_addr,
- self->parent_dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_server_parent, &parent_stream_address->unix_addr,
+ parent_stream_address->unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server_parent, &parent_dgram_address->unix_addr,
+ parent_dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server_parent, backlog));
@@ -734,57 +766,119 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
_metadata->exit_code = KSFT_FAIL;
}
+TEST_F(scoped_vs_unscoped, unix_scoping_abstract)
+{
+ test_scoped_vs_unscoped(_metadata, self, variant, true);
+}
+
+TEST_F(scoped_vs_unscoped, unix_scoping_pathname)
+{
+ test_scoped_vs_unscoped(_metadata, self, variant, false);
+}
+
FIXTURE(outside_socket)
{
- struct service_fixture address, transit_address;
+ struct service_fixture address_abstract, transit_address_abstract;
+ struct service_fixture address_pathname, transit_address_pathname;
};
FIXTURE_VARIANT(outside_socket)
{
const bool child_socket;
const int type;
+ const bool abstract;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_dgram_child) {
+ /* clang-format on */
+ .child_socket = true,
+ .type = SOCK_DGRAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_dgram_server) {
+ /* clang-format on */
+ .child_socket = false,
+ .type = SOCK_DGRAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_stream_child) {
+ /* clang-format on */
+ .child_socket = true,
+ .type = SOCK_STREAM,
+ .abstract = true,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_child) {
+FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_stream_server) {
+ /* clang-format on */
+ .child_socket = false,
+ .type = SOCK_STREAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_dgram_child) {
/* clang-format on */
.child_socket = true,
.type = SOCK_DGRAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_dgram_server) {
/* clang-format on */
.child_socket = false,
.type = SOCK_DGRAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, allow_stream_child) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_stream_child) {
/* clang-format on */
.child_socket = true,
.type = SOCK_STREAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_stream_server) {
/* clang-format on */
.child_socket = false,
.type = SOCK_STREAM,
+ .abstract = false,
};
FIXTURE_SETUP(outside_socket)
{
drop_caps(_metadata);
- memset(&self->transit_address, 0, sizeof(self->transit_address));
- set_unix_address(&self->transit_address, 0, true);
- memset(&self->address, 0, sizeof(self->address));
- set_unix_address(&self->address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ /* Abstract addresses. */
+ memset(&self->transit_address_abstract, 0,
+ sizeof(self->transit_address_abstract));
+ set_unix_address(&self->transit_address_abstract, 0, true);
+ memset(&self->address_abstract, 0, sizeof(self->address_abstract));
+ set_unix_address(&self->address_abstract, 1, true);
+
+ /* Pathname addresses. */
+ memset(&self->transit_address_pathname, 0,
+ sizeof(self->transit_address_pathname));
+ set_unix_address(&self->transit_address_pathname, 2, false);
+ memset(&self->address_pathname, 0, sizeof(self->address_pathname));
+ set_unix_address(&self->address_pathname, 3, false);
}
FIXTURE_TEARDOWN(outside_socket)
{
+ EXPECT_EQ(0, remove_path(self->transit_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
@@ -798,6 +892,15 @@ TEST_F(outside_socket, socket_with_different_domain)
int pipe_child[2], pipe_parent[2];
char buf_parent;
int server_socket;
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *transit_address =
+ variant->abstract ? &self->transit_address_abstract :
+ &self->transit_address_pathname;
+ const struct service_fixture *address =
+ variant->abstract ? &self->address_abstract :
+ &self->address_pathname;
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
@@ -812,8 +915,7 @@ TEST_F(outside_socket, socket_with_different_domain)
EXPECT_EQ(0, close(pipe_child[0]));
/* Client always has a domain. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (variant->child_socket) {
int data_socket, passed_socket, stream_server;
@@ -823,8 +925,8 @@ TEST_F(outside_socket, socket_with_different_domain)
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server);
ASSERT_EQ(0, bind(stream_server,
- &self->transit_address.unix_addr,
- self->transit_address.unix_addr_len));
+ &transit_address->unix_addr,
+ transit_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server, backlog));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
data_socket = accept(stream_server, NULL, NULL);
@@ -839,8 +941,8 @@ TEST_F(outside_socket, socket_with_different_domain)
/* Waits for parent signal for connection. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
- err = connect(client_socket, &self->address.unix_addr,
- self->address.unix_addr_len);
+ err = connect(client_socket, &address->unix_addr,
+ address->unix_addr_len);
if (variant->child_socket) {
EXPECT_EQ(0, err);
} else {
@@ -859,9 +961,8 @@ TEST_F(outside_socket, socket_with_different_domain)
ASSERT_LE(0, client_child);
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
- ASSERT_EQ(0, connect(client_child,
- &self->transit_address.unix_addr,
- self->transit_address.unix_addr_len));
+ ASSERT_EQ(0, connect(client_child, &transit_address->unix_addr,
+ transit_address->unix_addr_len));
server_socket = recv_fd(client_child);
EXPECT_EQ(0, close(client_child));
} else {
@@ -870,10 +971,10 @@ TEST_F(outside_socket, socket_with_different_domain)
ASSERT_LE(0, server_socket);
/* Server always has a domain. */
- create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
- ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr,
- self->address.unix_addr_len));
+ ASSERT_EQ(0,
+ bind(server_socket, &address->unix_addr, address->unix_addr_len));
if (variant->type == SOCK_STREAM)
ASSERT_EQ(0, listen(server_socket, backlog));
@@ -888,52 +989,85 @@ TEST_F(outside_socket, socket_with_different_domain)
_metadata->exit_code = KSFT_FAIL;
}
-static const char stream_path[] = TMP_DIR "/stream.sock";
-static const char dgram_path[] = TMP_DIR "/dgram.sock";
-
/* clang-format off */
-FIXTURE(various_address_sockets) {};
+FIXTURE(various_address_sockets) {
+ struct service_fixture stream_pathname_addr, dgram_pathname_addr;
+ struct service_fixture stream_abstract_addr, dgram_abstract_addr;
+};
/* clang-format on */
-FIXTURE_VARIANT(various_address_sockets)
-{
- const int domain;
+/*
+ * Test all 4 combinations of abstract and pathname socket scope bits,
+ * plus a case with no Landlock domain at all.
+ */
+/* clang-format off */
+FIXTURE_VARIANT(various_address_sockets) {
+ /* clang-format on */
+ const __u16 scope_bits;
+ const bool no_sandbox;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_abstract) {
+ /* clang-format on */
+ .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_pathname) {
+ /* clang-format on */
+ .scope_bits = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_scoped_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_both) {
/* clang-format on */
- .domain = SCOPE_SANDBOX,
+ .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_other_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_none) {
/* clang-format on */
- .domain = OTHER_SANDBOX,
+ .scope_bits = 0,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_no_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, no_domain) {
/* clang-format on */
- .domain = NO_SANDBOX,
+ .no_sandbox = true,
};
FIXTURE_SETUP(various_address_sockets)
{
drop_caps(_metadata);
- umask(0077);
- ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->stream_pathname_addr, 0, sizeof(self->stream_pathname_addr));
+ set_unix_address(&self->stream_pathname_addr, 0, false);
+ memset(&self->dgram_pathname_addr, 0, sizeof(self->dgram_pathname_addr));
+ set_unix_address(&self->dgram_pathname_addr, 1, false);
+
+ memset(&self->stream_abstract_addr, 0, sizeof(self->stream_abstract_addr));
+ set_unix_address(&self->stream_abstract_addr, 2, true);
+ memset(&self->dgram_abstract_addr, 0, sizeof(self->dgram_abstract_addr));
+ set_unix_address(&self->dgram_abstract_addr, 3, true);
}
FIXTURE_TEARDOWN(various_address_sockets)
{
- EXPECT_EQ(0, unlink(stream_path));
- EXPECT_EQ(0, unlink(dgram_path));
- EXPECT_EQ(0, rmdir(TMP_DIR));
+ EXPECT_EQ(0, remove_path(self->stream_pathname_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->dgram_pathname_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
-TEST_F(various_address_sockets, scoped_pathname_sockets)
+/*
+ * Test interaction of various scope flags (controlled by variant->domain)
+ * with pathname and abstract sockets when connecting from a sandboxed
+ * child.
+ */
+TEST_F(various_address_sockets, scoped_sockets)
{
pid_t child;
int status;
@@ -942,25 +1076,10 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
int unnamed_sockets[2];
int stream_pathname_socket, dgram_pathname_socket,
stream_abstract_socket, dgram_abstract_socket, data_socket;
- struct service_fixture stream_abstract_addr, dgram_abstract_addr;
- struct sockaddr_un stream_pathname_addr = {
- .sun_family = AF_UNIX,
- };
- struct sockaddr_un dgram_pathname_addr = {
- .sun_family = AF_UNIX,
- };
-
- /* Pathname address. */
- snprintf(stream_pathname_addr.sun_path,
- sizeof(stream_pathname_addr.sun_path), "%s", stream_path);
- snprintf(dgram_pathname_addr.sun_path,
- sizeof(dgram_pathname_addr.sun_path), "%s", dgram_path);
-
- /* Abstract address. */
- memset(&stream_abstract_addr, 0, sizeof(stream_abstract_addr));
- set_unix_address(&stream_abstract_addr, 0, true);
- memset(&dgram_abstract_addr, 0, sizeof(dgram_abstract_addr));
- set_unix_address(&dgram_abstract_addr, 1, true);
+ bool pathname_restricted =
+ (variant->scope_bits & LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
+ bool abstract_restricted =
+ (variant->scope_bits & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
/* Unnamed address for datagram socket. */
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets));
@@ -975,82 +1094,103 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
EXPECT_EQ(0, close(pipe_parent[1]));
EXPECT_EQ(0, close(unnamed_sockets[1]));
- if (variant->domain == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
- else if (variant->domain == OTHER_SANDBOX)
+ /* Create domain based on variant. */
+ if (variant->scope_bits)
+ create_scoped_domain(_metadata, variant->scope_bits);
+ else if (!variant->no_sandbox)
create_fs_domain(_metadata);
/* Waits for parent to listen. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
EXPECT_EQ(0, close(pipe_parent[0]));
- /* Checks that we can send data through a datagram socket. */
+ /* Checks that we can send data through a unnamed socket. */
ASSERT_EQ(1, write(unnamed_sockets[0], "a", 1));
EXPECT_EQ(0, close(unnamed_sockets[0]));
/* Connects with pathname sockets. */
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_pathname_socket);
- ASSERT_EQ(0,
- connect(stream_pathname_socket, &stream_pathname_addr,
- sizeof(stream_pathname_addr)));
- ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
+ err = connect(stream_pathname_socket,
+ &self->stream_pathname_addr.unix_addr,
+ self->stream_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
+ }
EXPECT_EQ(0, close(stream_pathname_socket));
- /* Sends without connection. */
+ /* Sends without connection (pathname). */
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_pathname_socket);
err = sendto(dgram_pathname_socket, "c", 1, 0,
- &dgram_pathname_addr, sizeof(dgram_pathname_addr));
- EXPECT_EQ(1, err);
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(1, err);
+ }
+
+ /* Sends with connection (pathname). */
+ err = connect(dgram_pathname_socket,
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
+ }
- /* Sends with connection. */
- ASSERT_EQ(0,
- connect(dgram_pathname_socket, &dgram_pathname_addr,
- sizeof(dgram_pathname_addr)));
- ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
EXPECT_EQ(0, close(dgram_pathname_socket));
/* Connects with abstract sockets. */
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_abstract_socket);
err = connect(stream_abstract_socket,
- &stream_abstract_addr.unix_addr,
- stream_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->stream_abstract_addr.unix_addr,
+ self->stream_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(0, err);
ASSERT_EQ(1, write(stream_abstract_socket, "e", 1));
}
+
EXPECT_EQ(0, close(stream_abstract_socket));
- /* Sends without connection. */
+ /* Sends without connection (abstract). */
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_abstract_socket);
err = sendto(dgram_abstract_socket, "f", 1, 0,
- &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(1, err);
}
- /* Sends with connection. */
+ /* Sends with connection (abstract). */
err = connect(dgram_abstract_socket,
- &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(0, err);
ASSERT_EQ(1, write(dgram_abstract_socket, "g", 1));
}
+
EXPECT_EQ(0, close(dgram_abstract_socket));
_exit(_metadata->exit_code);
@@ -1062,27 +1202,30 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
/* Sets up pathname servers. */
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_pathname_socket);
- ASSERT_EQ(0, bind(stream_pathname_socket, &stream_pathname_addr,
- sizeof(stream_pathname_addr)));
+ ASSERT_EQ(0, bind(stream_pathname_socket,
+ &self->stream_pathname_addr.unix_addr,
+ self->stream_pathname_addr.unix_addr_len));
ASSERT_EQ(0, listen(stream_pathname_socket, backlog));
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_pathname_socket);
- ASSERT_EQ(0, bind(dgram_pathname_socket, &dgram_pathname_addr,
- sizeof(dgram_pathname_addr)));
+ ASSERT_EQ(0, bind(dgram_pathname_socket,
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len));
/* Sets up abstract servers. */
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_abstract_socket);
- ASSERT_EQ(0,
- bind(stream_abstract_socket, &stream_abstract_addr.unix_addr,
- stream_abstract_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_abstract_socket,
+ &self->stream_abstract_addr.unix_addr,
+ self->stream_abstract_addr.unix_addr_len));
+ ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_abstract_socket);
- ASSERT_EQ(0, bind(dgram_abstract_socket, &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len));
- ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
+ ASSERT_EQ(0, bind(dgram_abstract_socket,
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
EXPECT_EQ(0, close(pipe_parent[1]));
@@ -1092,24 +1235,31 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
ASSERT_EQ('a', buf_parent);
EXPECT_LE(0, close(unnamed_sockets[1]));
- /* Reads from pathname sockets. */
- data_socket = accept(stream_pathname_socket, NULL, NULL);
- ASSERT_LE(0, data_socket);
- ASSERT_EQ(1, read(data_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('b', buf_parent);
- EXPECT_EQ(0, close(data_socket));
- EXPECT_EQ(0, close(stream_pathname_socket));
+ if (!pathname_restricted) {
+ /*
+ * Reads from pathname sockets if we expect child to be able to
+ * send.
+ */
+ data_socket = accept(stream_pathname_socket, NULL, NULL);
+ ASSERT_LE(0, data_socket);
+ ASSERT_EQ(1,
+ read(data_socket, &buf_parent, sizeof(buf_parent)));
+ ASSERT_EQ('b', buf_parent);
+ EXPECT_EQ(0, close(data_socket));
- ASSERT_EQ(1,
- read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('c', buf_parent);
- ASSERT_EQ(1,
- read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('d', buf_parent);
- EXPECT_EQ(0, close(dgram_pathname_socket));
+ ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
+ sizeof(buf_parent)));
+ ASSERT_EQ('c', buf_parent);
+ ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
+ sizeof(buf_parent)));
+ ASSERT_EQ('d', buf_parent);
+ }
- if (variant->domain != SCOPE_SANDBOX) {
- /* Reads from abstract sockets if allowed to send. */
+ if (!abstract_restricted) {
+ /*
+ * Reads from abstract sockets if we expect child to be able to
+ * send.
+ */
data_socket = accept(stream_abstract_socket, NULL, NULL);
ASSERT_LE(0, data_socket);
ASSERT_EQ(1,
@@ -1125,30 +1275,73 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
ASSERT_EQ('g', buf_parent);
}
- /* Waits for all abstract socket tests. */
+ /* Waits for child to complete, and only close the socket afterwards. */
ASSERT_EQ(child, waitpid(child, &status, 0));
EXPECT_EQ(0, close(stream_abstract_socket));
EXPECT_EQ(0, close(dgram_abstract_socket));
+ EXPECT_EQ(0, close(stream_pathname_socket));
+ EXPECT_EQ(0, close(dgram_pathname_socket));
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
}
-TEST(datagram_sockets)
+/* Fixture for datagram_sockets and self_connect tests */
+FIXTURE(socket_type_test)
{
struct service_fixture connected_addr, non_connected_addr;
+};
+
+FIXTURE_VARIANT(socket_type_test)
+{
+ const bool abstract;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_type_test, abstract) {
+ /* clang-format on */
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_type_test, pathname) {
+ /* clang-format on */
+ .abstract = false,
+};
+
+FIXTURE_SETUP(socket_type_test)
+{
+ drop_caps(_metadata);
+
+ if (!variant->abstract)
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->connected_addr, 0, sizeof(self->connected_addr));
+ set_unix_address(&self->connected_addr, 0, variant->abstract);
+ memset(&self->non_connected_addr, 0, sizeof(self->non_connected_addr));
+ set_unix_address(&self->non_connected_addr, 1, variant->abstract);
+}
+
+FIXTURE_TEARDOWN(socket_type_test)
+{
+ if (!variant->abstract) {
+ EXPECT_EQ(0, remove_path(self->connected_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->non_connected_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
+ }
+}
+
+TEST_F(socket_type_test, datagram_sockets)
+{
int server_conn_socket, server_unconn_socket;
int pipe_parent[2], pipe_child[2];
int status;
char buf;
pid_t child;
-
- drop_caps(_metadata);
- memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0, true);
- memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1, true);
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
@@ -1169,8 +1362,9 @@ TEST(datagram_sockets)
/* Waits for parent to listen. */
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
ASSERT_EQ(0,
- connect(client_conn_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ connect(client_conn_socket,
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
/*
* Both connected and non-connected sockets can send data when
@@ -1178,13 +1372,12 @@ TEST(datagram_sockets)
*/
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
ASSERT_EQ(1, sendto(client_unconn_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
/* Scopes the domain. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/*
* Connected socket sends data to the receiver, but the
@@ -1192,8 +1385,8 @@ TEST(datagram_sockets)
*/
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
ASSERT_EQ(-1, sendto(client_unconn_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -1210,10 +1403,11 @@ TEST(datagram_sockets)
ASSERT_LE(0, server_conn_socket);
ASSERT_LE(0, server_unconn_socket);
- ASSERT_EQ(0, bind(server_conn_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
- ASSERT_EQ(0, bind(server_unconn_socket, &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(server_conn_socket, &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(server_unconn_socket,
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
/* Waits for child to test. */
@@ -1238,52 +1432,49 @@ TEST(datagram_sockets)
_metadata->exit_code = KSFT_FAIL;
}
-TEST(self_connect)
+TEST_F(socket_type_test, self_connect)
{
- struct service_fixture connected_addr, non_connected_addr;
int connected_socket, non_connected_socket, status;
pid_t child;
-
- drop_caps(_metadata);
- memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0, true);
- memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1, true);
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, connected_socket);
ASSERT_LE(0, non_connected_socket);
- ASSERT_EQ(0, bind(connected_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
- ASSERT_EQ(0, bind(non_connected_socket, &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(connected_socket, &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(non_connected_socket,
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
/* Child's domain is scoped. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/*
* The child inherits the sockets, and cannot connect or
* send data to them.
*/
ASSERT_EQ(-1,
- connect(connected_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ connect(connected_socket,
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(-1, sendto(connected_socket, ".", 1, 0,
- &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(-1, sendto(non_connected_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
EXPECT_EQ(0, close(connected_socket));
--
2.52.0
^ permalink raw reply related [flat|nested] 9+ messages in thread