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@xxxxxxxxxx> Cc: Tahera Fahimi <fahimitahera@xxxxxxxxx> Cc: stable@xxxxxxxxxxxxxxx Signed-off-by: Mickaël Salaün <mic@xxxxxxxxxxx> Link: https://lore.kernel.org/r/20250318161443.279194-8-mic@xxxxxxxxxxx --- 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