[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250318161443.279194-8-mic@digikod.net>
Date: Tue, 18 Mar 2025 17:14:42 +0100
From: Mickaël Salaün <mic@...ikod.net>
To: Dan Carpenter <dan.carpenter@...aro.org>,
Günther Noack <gnoack@...gle.com>,
Paul Moore <paul@...l-moore.com>,
"Serge E . Hallyn" <serge@...lyn.com>
Cc: Mickaël Salaün <mic@...ikod.net>,
Christian Brauner <brauner@...nel.org>,
Jann Horn <jannh@...gle.com>,
Jeff Xu <jeffxu@...gle.com>,
Kees Cook <kees@...nel.org>,
Mikhail Ivanov <ivanov.mikhail1@...wei-partners.com>,
Tahera Fahimi <fahimitahera@...il.com>,
linux-fsdevel@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-security-module@...r.kernel.org,
stable@...r.kernel.org
Subject: [PATCH v2 7/8] selftests/landlock: Add a new test for setuid()
The new signal_scoping_thread_setuid tests check that the libc's
setuid() function works as expected even when a thread is sandboxed with
scoped signal restrictions.
Before the signal scoping fix, this test would have failed with the
setuid() call:
[pid 65] getpid() = 65
[pid 65] tgkill(65, 66, SIGRT_1) = -1 EPERM (Operation not permitted)
[pid 65] futex(0x40a66cdc, FUTEX_WAKE_PRIVATE, 1) = 0
[pid 65] setuid(1001) = 0
After the fix, tgkill(2) is successfully leveraged to synchronize
credentials update across threads:
[pid 65] getpid() = 65
[pid 65] tgkill(65, 66, SIGRT_1) = 0
[pid 66] <... read resumed>0x40a65eb7, 1) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 66] --- SIGRT_1 {si_signo=SIGRT_1, si_code=SI_TKILL, si_pid=65, si_uid=1000} ---
[pid 66] getpid() = 65
[pid 66] setuid(1001) = 0
[pid 66] futex(0x40a66cdc, FUTEX_WAKE_PRIVATE, 1) = 0
[pid 66] rt_sigreturn({mask=[]}) = 0
[pid 66] read(3, <unfinished ...>
[pid 65] setuid(1001) = 0
Test coverage for security/landlock is 92.9% of 1053 lines according to
gcc/gcov-14.
Fixes: c8994965013e ("selftests/landlock: Test signal scoping for threads")
Cc: Günther Noack <gnoack@...gle.com>
Cc: Tahera Fahimi <fahimitahera@...il.com>
Cc: stable@...r.kernel.org
Signed-off-by: Mickaël Salaün <mic@...ikod.net>
Link: https://lore.kernel.org/r/20250318161443.279194-8-mic@digikod.net
---
Changes since v1:
- New patch.
---
tools/testing/selftests/landlock/common.h | 1 +
.../selftests/landlock/scoped_signal_test.c | 59 +++++++++++++++++++
2 files changed, 60 insertions(+)
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 6064c9ac0532..076a9a625c98 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -41,6 +41,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
CAP_MKNOD,
CAP_NET_ADMIN,
CAP_NET_BIND_SERVICE,
+ CAP_SETUID,
CAP_SYS_ADMIN,
CAP_SYS_CHROOT,
/* clang-format on */
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index d313cb626225..d8bf33417619 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -253,6 +253,7 @@ enum thread_return {
THREAD_INVALID = 0,
THREAD_SUCCESS = 1,
THREAD_ERROR = 2,
+ THREAD_TEST_FAILED = 3,
};
static void *thread_sync(void *arg)
@@ -316,6 +317,64 @@ TEST(signal_scoping_thread_after)
EXPECT_EQ(0, close(thread_pipe[1]));
}
+struct thread_setuid_args {
+ int pipe_read, new_uid;
+};
+
+void *thread_setuid(void *ptr)
+{
+ const struct thread_setuid_args *arg = ptr;
+ char buf;
+
+ if (read(arg->pipe_read, &buf, 1) != 1)
+ return (void *)THREAD_ERROR;
+
+ /* libc's setuid() should update all thread's credentials. */
+ if (getuid() != arg->new_uid)
+ return (void *)THREAD_TEST_FAILED;
+
+ return (void *)THREAD_SUCCESS;
+}
+
+TEST(signal_scoping_thread_setuid)
+{
+ struct thread_setuid_args arg;
+ pthread_t no_sandbox_thread;
+ enum thread_return ret = THREAD_INVALID;
+ int pipe_parent[2];
+ int prev_uid;
+
+ disable_caps(_metadata);
+
+ /* This test does not need to be run as root. */
+ prev_uid = getuid();
+ arg.new_uid = prev_uid + 1;
+ EXPECT_LT(0, arg.new_uid);
+
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ arg.pipe_read = pipe_parent[0];
+
+ /* Capabilities must be set before creating a new thread. */
+ set_cap(_metadata, CAP_SETUID);
+ ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_setuid,
+ &arg));
+
+ /* Enforces restriction after creating the thread. */
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+
+ EXPECT_NE(arg.new_uid, getuid());
+ EXPECT_EQ(0, setuid(arg.new_uid));
+ EXPECT_EQ(arg.new_uid, getuid());
+ EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
+
+ EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
+ EXPECT_EQ(THREAD_SUCCESS, ret);
+
+ clear_cap(_metadata, CAP_SETUID);
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ EXPECT_EQ(0, close(pipe_parent[1]));
+}
+
const short backlog = 10;
static volatile sig_atomic_t signal_received;
--
2.48.1
Powered by blists - more mailing lists