[PATCH v2 2/3] selftests/landlock: Test LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
From: Justin Suess <hidden>
Date: 2026-07-17 22:03:33
Also in:
lkml
Subsystem:
kernel selftest framework, landlock security module, the rest · Maintainers:
Shuah Khan, Mickaël Salaün, Linus Torvalds
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 leaves the attribute unchanged, and that LANDLOCK_RESTRICT_SELF_TSYNC extends it to sibling threads. Also check that this flag requires a ruleset, and update the restrict_self_checks_ordering EPERM checks since this flag is now checked before the flags validity. Update the ABI version and last-flag checks accordingly. Signed-off-by: Justin Suess <redacted> --- tools/testing/selftests/landlock/base_test.c | 65 +++++++++++++++++-- tools/testing/selftests/landlock/tsync_test.c | 33 ++++++++++ 2 files changed, 93 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index cbd3c1669951..2d4903588903 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c@@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(10, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(11, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
@@ -255,8 +255,15 @@ TEST(restrict_self_checks_ordering) /* Checks unprivileged enforcement without no_new_privs. */ drop_caps(_metadata); - ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); + ASSERT_EQ(-1, landlock_restrict_self( + -1, ~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)); ASSERT_EQ(EPERM, errno); + /* + * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills the no_new_privs / + * CAP_SYS_ADMIN requirement, so the invalid flags are checked first. + */ + ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); + ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); ASSERT_EQ(EPERM, errno); ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0));
@@ -288,7 +295,7 @@ TEST(restrict_self_fd) EXPECT_EQ(EBADFD, errno); } -TEST(restrict_self_fd_logging_flags) +TEST(restrict_self_fd_flags) { int fd;
@@ -302,11 +309,16 @@ 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_TSYNC; + const __u32 last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS; /* Tests invalid flag combinations. */
@@ -349,6 +361,18 @@ 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(
@@ -359,6 +383,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..d5336186b2c7 100644
--- a/tools/testing/selftests/landlock/tsync_test.c
+++ b/tools/testing/selftests/landlock/tsync_test.c@@ -90,6 +90,39 @@ TEST(multi_threaded_success) EXPECT_EQ(0, close(ruleset_fd)); } +TEST(multi_threaded_no_new_privs) +{ + pthread_t t1, t2; + bool no_new_privs1, no_new_privs2; + const int ruleset_fd = create_ruleset(_metadata); + + disable_caps(_metadata); + + ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1)); + ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2)); + + /* 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 | + LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)); + + EXPECT_EQ(1, 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 enabled on all threads. */ + EXPECT_TRUE(no_new_privs1); + EXPECT_TRUE(no_new_privs2); + + EXPECT_EQ(0, close(ruleset_fd)); +} + TEST(multi_threaded_success_despite_diverging_domains) { pthread_t t1, t2;
--
2.54.0