[PATCH v4 3/5] selftests/landlock: Test LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS

Justin Suess <[email protected]>
Newsgroups org.kernel.vger.linux-security-module,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Check that a successful landlock_restrict_self(2) call with
LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs without a prior
prctl(2) call nor CAP_SYS_ADMIN, that a failed call from both an
invalid ruleset and hitting the layer maximum leaves the attribute
unchanged, and that LANDLOCK_RESTRICT_SELF_TSYNC extends it to sibling
threads.  Also check that this flag requires a ruleset.

Turn the multi_threaded_success test into a multi_threaded fixture with
success, no_new_privs, and no_new_privs_max_layers variants to factor
out the threading code.

Finally, rename restrict_self_fd_logging_flags to
restrict_self_fd_flags, and restrict_self_logging_flags to
restrict_self_flags to indicate that non-logging flags are now tested.

Signed-off-by: Justin Suess <[email protected]>
---

Notes:
    v3->v4:
        - Turn multi_threaded_{success,no_new_privs,no_new_privs_max_layers}
          into variants of a multi_threaded fixture, per Mickaël's
          feedback.
        - Move the minimal ABI/flag checks into the previous patch.

 tools/testing/selftests/landlock/base_test.c  | 86 ++++++++++++++++-
 tools/testing/selftests/landlock/tsync_test.c | 96 ++++++++++++++++---
 2 files changed, 168 insertions(+), 14 deletions(-)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 288d6bc19232..d20ab8f0862c 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -289,6 +289,41 @@ TEST(restrict_self_checks_ordering)
 	ASSERT_EQ(0, close(ruleset_fd));
 }
 
+TEST(restrict_self_max_layers)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
+	};
+	struct landlock_path_beneath_attr path_beneath_attr = {
+		.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE,
+		.parent_fd = -1,
+	};
+	const int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	path_beneath_attr.parent_fd =
+		open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_LE(0, path_beneath_attr.parent_fd);
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+				       &path_beneath_attr, 0));
+	ASSERT_EQ(0, close(path_beneath_attr.parent_fd));
+
+	/* Enforces the maximum number of allowed layers. */
+	for (int i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
+		ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+
+	/* Enforces one too many rulesets. */
+	drop_caps(_metadata);
+	ASSERT_EQ(-1, landlock_restrict_self(
+			      ruleset_fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	ASSERT_EQ(E2BIG, errno);
+
+	/* Checks that the failed call did not set no_new_privs. */
+	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+	ASSERT_EQ(0, close(ruleset_fd));
+}
+
 TEST(restrict_self_fd)
 {
 	int fd;
@@ -300,7 +335,7 @@ TEST(restrict_self_fd)
 	EXPECT_EQ(EBADFD, errno);
 }
 
-TEST(restrict_self_fd_logging_flags)
+TEST(restrict_self_fd_flags)
 {
 	int fd;
 
@@ -314,9 +349,14 @@ TEST(restrict_self_fd_logging_flags)
 	EXPECT_EQ(-1, landlock_restrict_self(
 			      fd, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
 	EXPECT_EQ(EBADFD, errno);
+
+	/* LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS requires a ruleset FD. */
+	EXPECT_EQ(-1, landlock_restrict_self(
+			      fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	EXPECT_EQ(EBADFD, errno);
 }
 
-TEST(restrict_self_logging_flags)
+TEST(restrict_self_flags)
 {
 	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
 
@@ -361,6 +401,17 @@ TEST(restrict_self_logging_flags)
 				      LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON));
 	EXPECT_EQ(EBADF, errno);
 
+	/* LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS requires a ruleset FD. */
+
+	EXPECT_EQ(-1, landlock_restrict_self(
+			      -1, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	EXPECT_EQ(EBADF, errno);
+
+	EXPECT_EQ(-1, landlock_restrict_self(
+			      -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
+					  LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	EXPECT_EQ(EBADF, errno);
+
 	/* Tests with an invalid ruleset_fd. */
 
 	EXPECT_EQ(-1, landlock_restrict_self(
@@ -371,6 +422,37 @@ TEST(restrict_self_logging_flags)
 			     -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
 }
 
+TEST(restrict_self_no_new_privs)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+	};
+	const int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+
+	ASSERT_LE(0, ruleset_fd);
+
+	/*
+	 * The calling thread does not need CAP_SYS_ADMIN nor an explicit
+	 * prctl(2) PR_SET_NO_NEW_PRIVS call.
+	 */
+	drop_caps(_metadata);
+	ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+
+	/* Checks that a failed call does not set no_new_privs. */
+	EXPECT_EQ(-1, landlock_restrict_self(
+			      -1, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	EXPECT_EQ(EBADF, errno);
+	EXPECT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+
+	/* Checks that a successful call sets no_new_privs. */
+	ASSERT_EQ(0, landlock_restrict_self(
+			     ruleset_fd, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS));
+	EXPECT_EQ(1, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
 TEST(ruleset_fd_io)
 {
 	struct landlock_ruleset_attr ruleset_attr = {
diff --git a/tools/testing/selftests/landlock/tsync_test.c b/tools/testing/selftests/landlock/tsync_test.c
index 9cf1491bbaaf..2b53596c986e 100644
--- a/tools/testing/selftests/landlock/tsync_test.c
+++ b/tools/testing/selftests/landlock/tsync_test.c
@@ -62,32 +62,104 @@ static void *idle(void *data)
 	pthread_cleanup_pop(1);
 }
 
-TEST(multi_threaded_success)
+FIXTURE(multi_threaded)
 {
-	pthread_t t1, t2;
-	bool no_new_privs1, no_new_privs2;
-	const int ruleset_fd = create_ruleset(_metadata);
+	int ruleset_fd;
+};
+
+FIXTURE_VARIANT(multi_threaded)
+{
+	const __u32 restrict_flags;
+	/* Sets no_new_privs with prctl(2) before the enforcement. */
+	const bool prior_no_new_privs;
+	/* Enforces the maximum number of allowed layers beforehand. */
+	const bool max_layers;
+	const int expected_errno;
+	/* Expected no_new_privs state of all threads after the call. */
+	const bool expected_no_new_privs;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(multi_threaded, success) {
+	/* clang-format on */
+	.restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC,
+	.prior_no_new_privs = true,
+	.expected_no_new_privs = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(multi_threaded, no_new_privs) {
+	/* clang-format on */
+	.restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC |
+			  LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
+	.expected_no_new_privs = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(multi_threaded, no_new_privs_max_layers) {
+	/* clang-format on */
+	.restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC |
+			  LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
+	.max_layers = true,
+	.expected_errno = E2BIG,
+	.expected_no_new_privs = false,
+};
+
+FIXTURE_SETUP(multi_threaded)
+{
+	self->ruleset_fd = create_ruleset(_metadata);
+
+	if (variant->max_layers) {
+		/* Enforces the maximum number of allowed layers. */
+		for (int i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
+			ASSERT_EQ(0,
+				  landlock_restrict_self(self->ruleset_fd, 0));
+	}
 
 	disable_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(multi_threaded)
+{
+	EXPECT_EQ(0, close(self->ruleset_fd));
+}
+
+TEST_F(multi_threaded, restrict)
+{
+	pthread_t t1, t2;
+	bool no_new_privs1, no_new_privs2;
 
 	ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1));
 	ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2));
 
-	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	if (variant->prior_no_new_privs) {
+		ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	} else {
+		/* No prior prctl(2) PR_SET_NO_NEW_PRIVS call. */
+		ASSERT_EQ(0, prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+	}
 
-	EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
-					    LANDLOCK_RESTRICT_SELF_TSYNC));
+	if (variant->expected_errno) {
+		EXPECT_EQ(-1, landlock_restrict_self(self->ruleset_fd,
+						     variant->restrict_flags));
+		EXPECT_EQ(variant->expected_errno, errno);
+	} else {
+		EXPECT_EQ(0, landlock_restrict_self(self->ruleset_fd,
+						    variant->restrict_flags));
+	}
+
+	/* Checks the no_new_privs state of the calling thread. */
+	EXPECT_EQ(variant->expected_no_new_privs,
+		  prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
 
 	ASSERT_EQ(0, pthread_cancel(t1));
 	ASSERT_EQ(0, pthread_cancel(t2));
 	ASSERT_EQ(0, pthread_join(t1, NULL));
 	ASSERT_EQ(0, pthread_join(t2, NULL));
 
-	/* The no_new_privs flag was implicitly enabled on all threads. */
-	EXPECT_TRUE(no_new_privs1);
-	EXPECT_TRUE(no_new_privs2);
-
-	EXPECT_EQ(0, close(ruleset_fd));
+	/* Checks the no_new_privs state of the sibling threads. */
+	EXPECT_EQ(variant->expected_no_new_privs, no_new_privs1);
+	EXPECT_EQ(variant->expected_no_new_privs, no_new_privs2);
 }
 
 TEST(multi_threaded_success_despite_diverging_domains)
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.