Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: c8994965013e ("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>

+60
+1
tools/testing/selftests/landlock/common.h
··· 41 41 CAP_MKNOD, 42 42 CAP_NET_ADMIN, 43 43 CAP_NET_BIND_SERVICE, 44 + CAP_SETUID, 44 45 CAP_SYS_ADMIN, 45 46 CAP_SYS_CHROOT, 46 47 /* clang-format on */
+59
tools/testing/selftests/landlock/scoped_signal_test.c
··· 253 253 THREAD_INVALID = 0, 254 254 THREAD_SUCCESS = 1, 255 255 THREAD_ERROR = 2, 256 + THREAD_TEST_FAILED = 3, 256 257 }; 257 258 258 259 static void *thread_sync(void *arg) ··· 315 314 316 315 EXPECT_EQ(0, close(thread_pipe[0])); 317 316 EXPECT_EQ(0, close(thread_pipe[1])); 317 + } 318 + 319 + struct thread_setuid_args { 320 + int pipe_read, new_uid; 321 + }; 322 + 323 + void *thread_setuid(void *ptr) 324 + { 325 + const struct thread_setuid_args *arg = ptr; 326 + char buf; 327 + 328 + if (read(arg->pipe_read, &buf, 1) != 1) 329 + return (void *)THREAD_ERROR; 330 + 331 + /* libc's setuid() should update all thread's credentials. */ 332 + if (getuid() != arg->new_uid) 333 + return (void *)THREAD_TEST_FAILED; 334 + 335 + return (void *)THREAD_SUCCESS; 336 + } 337 + 338 + TEST(signal_scoping_thread_setuid) 339 + { 340 + struct thread_setuid_args arg; 341 + pthread_t no_sandbox_thread; 342 + enum thread_return ret = THREAD_INVALID; 343 + int pipe_parent[2]; 344 + int prev_uid; 345 + 346 + disable_caps(_metadata); 347 + 348 + /* This test does not need to be run as root. */ 349 + prev_uid = getuid(); 350 + arg.new_uid = prev_uid + 1; 351 + EXPECT_LT(0, arg.new_uid); 352 + 353 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 354 + arg.pipe_read = pipe_parent[0]; 355 + 356 + /* Capabilities must be set before creating a new thread. */ 357 + set_cap(_metadata, CAP_SETUID); 358 + ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_setuid, 359 + &arg)); 360 + 361 + /* Enforces restriction after creating the thread. */ 362 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); 363 + 364 + EXPECT_NE(arg.new_uid, getuid()); 365 + EXPECT_EQ(0, setuid(arg.new_uid)); 366 + EXPECT_EQ(arg.new_uid, getuid()); 367 + EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); 368 + 369 + EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); 370 + EXPECT_EQ(THREAD_SUCCESS, ret); 371 + 372 + clear_cap(_metadata, CAP_SETUID); 373 + EXPECT_EQ(0, close(pipe_parent[0])); 374 + EXPECT_EQ(0, close(pipe_parent[1])); 318 375 } 319 376 320 377 const short backlog = 10;