]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/landlock: Add a new test for setuid()
authorMickaël Salaün <mic@digikod.net>
Tue, 18 Mar 2025 16:14:42 +0000 (17:14 +0100)
committerMickaël Salaün <mic@digikod.net>
Wed, 26 Mar 2025 12:59:32 +0000 (13:59 +0100)
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>
tools/testing/selftests/landlock/common.h
tools/testing/selftests/landlock/scoped_signal_test.c

index 6064c9ac05329d42bf70c3a1e6e2cd4b5b190bbb..076a9a625c98d1ba1825ff0ea076f58fecb4dc8e 100644 (file)
@@ -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 */
index d313cb6262259b8ed229baec46084f9565e5c655..d8bf33417619f61e73f3fdb23272741eac7eadf4 100644 (file)
@@ -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;