Skip to content

Commit c5efa39

Browse files
committed
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>
1 parent bbe7227 commit c5efa39

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
@@ -41,6 +41,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
4141
CAP_MKNOD,
4242
CAP_NET_ADMIN,
4343
CAP_NET_BIND_SERVICE,
44+
CAP_SETUID,
4445
CAP_SYS_ADMIN,
4546
CAP_SYS_CHROOT,
4647
/* 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,6 +317,64 @@ 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
const short backlog = 10;
320379

321380
static volatile sig_atomic_t signal_received;

0 commit comments

Comments
 (0)