From: "Mickaël Salaün" <mic@digikod.net>
To: "Paul Moore" <paul@paul-moore.com>,
"Günther Noack" <gnoack@google.com>,
"Serge E . Hallyn" <serge@hallyn.com>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@google.com>,
"Kees Cook" <kees@kernel.org>,
"Tahera Fahimi" <fahimitahera@gmail.com>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-security-module@vger.kernel.org,
"Christian Brauner" <brauner@kernel.org>
Subject: [RFC PATCH v1] landlock: Allow signals between threads of the same process
Date: Thu, 13 Mar 2025 15:59:04 +0100 [thread overview]
Message-ID: <20250313145904.3238184-1-mic@digikod.net> (raw)
Because Linux credentials are managed per thread, user space relies on
some hack to synchronize credential update across threads from the same
process. This is required by the Native POSIX Threads Library and
implemented by set*id(2) wrappers and libcap(3) to use tgkill(2) to
synchronize threads. See nptl(7) and libpsx(3). Furthermore, some
runtimes like Go do not enable developers to have control over threads
[1].
To avoid potential issues, and because threads are not security
boundaries, let's relax the Landlock signal scoping to always allow
signals sent between threads of the same process. This exception is
similar to the __ptrace_may_access() one.
hook_file_set_fowner() now checks if the target task is part of the same
process as the caller. If this is the case, then the related signal
triggered by the socket will always be allowed.
Scoping of abstract UNIX sockets is not changed because kernel objects
(e.g. sockets) should be tied to their creator's domain at creation
time.
Note that creating one Landlock domain per thread puts each of these
threads (and their future children) in their own scope, which is
probably not what users expect, especially in Go where we do not control
threads. However, being able to drop permissions on all threads should
not be restricted by signal scoping. We are working on a way to make it
possible to atomically restrict all threads of a process with the same
domain [2].
Closes: https://github.com/landlock-lsm/go-landlock/issues/36
Fixes: 54a6e6bbf3be ("landlock: Add signal scoping")
Fixes: c8994965013e ("selftests/landlock: Test signal scoping for threads")
Depends-on: 26f204380a3c ("fs: Fix file_set_fowner LSM hook inconsistencies")
Link: https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/psx [1]
Link: https://github.com/landlock-lsm/linux/issues/2 [2]
Cc: Christian Brauner <brauner@kernel.org>
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge Hallyn <serge@hallyn.com>
Cc: Tahera Fahimi <fahimitahera@gmail.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250313145904.3238184-1-mic@digikod.net
---
I'm still not sure how we could reliably detect if the running kernel
has this fix or not, especially in Go.
---
security/landlock/fs.c | 22 +++++++++++++++----
security/landlock/task.c | 12 ++++++++++
.../selftests/landlock/scoped_signal_test.c | 2 +-
3 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 71b9dc331aae..47c862fe14e4 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -27,7 +27,9 @@
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/path.h>
+#include <linux/pid.h>
#include <linux/rcupdate.h>
+#include <linux/sched/signal.h>
#include <linux/spinlock.h>
#include <linux/stat.h>
#include <linux/types.h>
@@ -1630,15 +1632,27 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
static void hook_file_set_fowner(struct file *file)
{
- struct landlock_ruleset *new_dom, *prev_dom;
+ struct fown_struct *fown = file_f_owner(file);
+ struct landlock_ruleset *new_dom = NULL;
+ struct landlock_ruleset *prev_dom;
+ struct task_struct *p;
/*
* Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix
* file_set_fowner LSM hook inconsistencies").
*/
- lockdep_assert_held(&file_f_owner(file)->lock);
- new_dom = landlock_get_current_domain();
- landlock_get_ruleset(new_dom);
+ lockdep_assert_held(&fown->lock);
+
+ /*
+ * Always allow sending signals between threads of the same process. This
+ * ensures consistency with hook_task_kill().
+ */
+ p = pid_task(fown->pid, fown->pid_type);
+ if (!same_thread_group(p, current)) {
+ new_dom = landlock_get_current_domain();
+ landlock_get_ruleset(new_dom);
+ }
+
prev_dom = landlock_file(file)->fown_domain;
landlock_file(file)->fown_domain = new_dom;
diff --git a/security/landlock/task.c b/security/landlock/task.c
index dc7dab78392e..4578ce6e319d 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -13,6 +13,7 @@
#include <linux/lsm_hooks.h>
#include <linux/rcupdate.h>
#include <linux/sched.h>
+#include <linux/sched/signal.h>
#include <net/af_unix.h>
#include <net/sock.h>
@@ -264,6 +265,17 @@ static int hook_task_kill(struct task_struct *const p,
/* Dealing with USB IO. */
dom = landlock_cred(cred)->domain;
} else {
+ /*
+ * Always allow sending signals between threads of the same process.
+ * This is required for process credential changes by the Native POSIX
+ * Threads Library and implemented by the set*id(2) wrappers and
+ * libcap(3) with tgkill(2). See nptl(7) and libpsx(3).
+ *
+ * This exception is similar to the __ptrace_may_access() one.
+ */
+ if (same_thread_group(p, current))
+ return 0;
+
dom = landlock_get_current_domain();
}
dom = landlock_get_applicable_domain(dom, signal_scope);
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index 475ee62a832d..767f117703b7 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -281,7 +281,7 @@ TEST(signal_scoping_threads)
/* Restricts the domain after creating the first thread. */
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
- ASSERT_EQ(EPERM, pthread_kill(no_sandbox_thread, 0));
+ ASSERT_EQ(0, pthread_kill(no_sandbox_thread, 0));
ASSERT_EQ(1, write(thread_pipe[1], ".", 1));
ASSERT_EQ(0, pthread_create(&scoped_thread, NULL, thread_func, NULL));
base-commit: 7eb172143d5508b4da468ed59ee857c6e5e01da6
--
2.48.1
next reply other threads:[~2025-03-13 14:59 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-13 14:59 Mickaël Salaün [this message]
2025-03-14 15:42 ` Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250313145904.3238184-1-mic@digikod.net \
--to=mic@digikod.net \
--cc=brauner@kernel.org \
--cc=fahimitahera@gmail.com \
--cc=gnoack@google.com \
--cc=jannh@google.com \
--cc=jeffxu@google.com \
--cc=kees@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®