Skip to content

Commit 2c780b1

Browse files
committed
selftests/landlock: Add a new test for setuid()
JIRA: https://issues.redhat.com/browse/RHEL-125143 Upstream Status: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git commit c5efa39 Author: Mickaël Salaün <mic@digikod.net> Date: Tue Mar 18 17:14:42 2025 +0100 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 1137 lines according to gcc/gcov-14. Fixes: c899496 ("selftests/landlock: Test signal scoping for threads") Cc: Günther Noack <gnoack@google.com> Cc: Tahera Fahimi <fahimitahera@gmail.com> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20250318161443.279194-8-mic@digikod.net [mic: Update test coverage] Signed-off-by: Mickaël Salaün <mic@digikod.net> Signed-off-by: Štěpán Horáček <shoracek@redhat.com>
1 parent 723871a commit 2c780b1

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

tools/testing/selftests/landlock/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
6868
CAP_MKNOD,
6969
CAP_NET_ADMIN,
7070
CAP_NET_BIND_SERVICE,
71+
CAP_SETUID,
7172
CAP_SYS_ADMIN,
7273
CAP_SYS_CHROOT,
7374
/* clang-format on */

tools/testing/selftests/landlock/scoped_signal_test.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ enum thread_return {
253253
THREAD_INVALID = 0,
254254
THREAD_SUCCESS = 1,
255255
THREAD_ERROR = 2,
256+
THREAD_TEST_FAILED = 3,
256257
};
257258

258259
static void *thread_sync(void *arg)
@@ -316,4 +317,62 @@ TEST(signal_scoping_thread_after)
316317
EXPECT_EQ(0, close(thread_pipe[1]));
317318
}
318319

320+
struct thread_setuid_args {
321+
int pipe_read, new_uid;
322+
};
323+
324+
void *thread_setuid(void *ptr)
325+
{
326+
const struct thread_setuid_args *arg = ptr;
327+
char buf;
328+
329+
if (read(arg->pipe_read, &buf, 1) != 1)
330+
return (void *)THREAD_ERROR;
331+
332+
/* libc's setuid() should update all thread's credentials. */
333+
if (getuid() != arg->new_uid)
334+
return (void *)THREAD_TEST_FAILED;
335+
336+
return (void *)THREAD_SUCCESS;
337+
}
338+
339+
TEST(signal_scoping_thread_setuid)
340+
{
341+
struct thread_setuid_args arg;
342+
pthread_t no_sandbox_thread;
343+
enum thread_return ret = THREAD_INVALID;
344+
int pipe_parent[2];
345+
int prev_uid;
346+
347+
disable_caps(_metadata);
348+
349+
/* This test does not need to be run as root. */
350+
prev_uid = getuid();
351+
arg.new_uid = prev_uid + 1;
352+
EXPECT_LT(0, arg.new_uid);
353+
354+
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
355+
arg.pipe_read = pipe_parent[0];
356+
357+
/* Capabilities must be set before creating a new thread. */
358+
set_cap(_metadata, CAP_SETUID);
359+
ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_setuid,
360+
&arg));
361+
362+
/* Enforces restriction after creating the thread. */
363+
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
364+
365+
EXPECT_NE(arg.new_uid, getuid());
366+
EXPECT_EQ(0, setuid(arg.new_uid));
367+
EXPECT_EQ(arg.new_uid, getuid());
368+
EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
369+
370+
EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
371+
EXPECT_EQ(THREAD_SUCCESS, ret);
372+
373+
clear_cap(_metadata, CAP_SETUID);
374+
EXPECT_EQ(0, close(pipe_parent[0]));
375+
EXPECT_EQ(0, close(pipe_parent[1]));
376+
}
377+
319378
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)