Skip to content

Commit 42fc7e6

Browse files
gnoackl0kod
authored andcommitted
landlock: Multithreading support for landlock_restrict_self()
Introduce the LANDLOCK_RESTRICT_SELF_TSYNC flag. With this flag, a given Landlock ruleset is applied to all threads of the calling process, instead of only the current one. Without this flag, multithreaded userspace programs currently resort to using the nptl(7)/libpsx hack for multithreaded policy enforcement, which is also used by libcap and for setuid(2). Using this userspace-based scheme, the threads of a process enforce the same Landlock policy, but the resulting Landlock domains are still separate. The domains being separate causes multiple problems: * When using Landlock's "scoped" access rights, the domain identity is used to determine whether an operation is permitted. As a result, when using LANLDOCK_SCOPE_SIGNAL, signaling between sibling threads stops working. This is a problem for programming languages and frameworks which are inherently multithreaded (e.g. Go). * In audit logging, the domains of separate threads in a process will get logged with different domain IDs, even when they are based on the same ruleset FD, which might confuse users. Cc: Andrew G. Morgan <morgan@kernel.org> Cc: John Johansen <john.johansen@canonical.com> Cc: Paul Moore <paul@paul-moore.com> Suggested-by: Jann Horn <jannh@google.com> Signed-off-by: Günther Noack <gnoack@google.com> Link: https://lore.kernel.org/r/20251127115136.3064948-2-gnoack@google.com [mic: Fix restrict_self_flags test, clean up Makefile, allign comments, reduce local variable scope, add missing includes] Closes: landlock-lsm#2 Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent 24d479d commit 42fc7e6

8 files changed

Lines changed: 650 additions & 30 deletions

File tree

include/uapi/linux/landlock.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,24 @@ struct landlock_ruleset_attr {
117117
* future nested domains, not the one being created. It can also be used
118118
* with a @ruleset_fd value of -1 to mute subdomain logs without creating a
119119
* domain.
120+
*
121+
* The following flag supports policy enforcement in multithreaded processes:
122+
*
123+
* %LANDLOCK_RESTRICT_SELF_TSYNC
124+
* Applies the new Landlock configuration atomically to all threads of the
125+
* current process, including the Landlock domain and logging
126+
* configuration. This overrides the Landlock configuration of sibling
127+
* threads, irrespective of previously established Landlock domains and
128+
* logging configurations on these threads.
129+
*
130+
* If the calling thread is running with no_new_privs, this operation
131+
* enables no_new_privs on the sibling threads as well.
120132
*/
121133
/* clang-format off */
122134
#define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0)
123135
#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
124136
#define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2)
137+
#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
125138
/* clang-format on */
126139

127140
/**

security/landlock/Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
22

3-
landlock-y := setup.o syscalls.o object.o ruleset.o \
4-
cred.o task.o fs.o
3+
landlock-y := \
4+
setup.o \
5+
syscalls.o \
6+
object.o \
7+
ruleset.o \
8+
cred.o \
9+
task.o \
10+
fs.o \
11+
tsync.o
512

613
landlock-$(CONFIG_INET) += net.o
714

security/landlock/cred.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
* This structure is packed to minimize the size of struct
2727
* landlock_file_security. However, it is always aligned in the LSM cred blob,
2828
* see lsm_set_blob_size().
29+
*
30+
* When updating this, also update landlock_cred_copy() if needed.
2931
*/
3032
struct landlock_cred_security {
3133
/**
@@ -65,6 +67,16 @@ landlock_cred(const struct cred *cred)
6567
return cred->security + landlock_blob_sizes.lbs_cred;
6668
}
6769

70+
static inline void landlock_cred_copy(struct landlock_cred_security *dst,
71+
const struct landlock_cred_security *src)
72+
{
73+
landlock_put_ruleset(dst->domain);
74+
75+
*dst = *src;
76+
77+
landlock_get_ruleset(src->domain);
78+
}
79+
6880
static inline struct landlock_ruleset *landlock_get_current_domain(void)
6981
{
7082
return landlock_cred(current_cred())->domain;

security/landlock/limits.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
3232
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
3333

34-
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
34+
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
3535
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
3636

3737
/* clang-format on */

security/landlock/syscalls.c

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "net.h"
3737
#include "ruleset.h"
3838
#include "setup.h"
39+
#include "tsync.h"
3940

4041
static bool is_initialized(void)
4142
{
@@ -161,7 +162,7 @@ static const struct file_operations ruleset_fops = {
161162
* Documentation/userspace-api/landlock.rst should be updated to reflect the
162163
* UAPI change.
163164
*/
164-
const int landlock_abi_version = 7;
165+
const int landlock_abi_version = 8;
165166

166167
/**
167168
* sys_landlock_create_ruleset - Create a new ruleset
@@ -454,9 +455,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
454455
* - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
455456
* - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
456457
* - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
458+
* - %LANDLOCK_RESTRICT_SELF_TSYNC
457459
*
458-
* This system call enables to enforce a Landlock ruleset on the current
459-
* thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
460+
* This system call enforces a Landlock ruleset on the current thread.
461+
* Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
460462
* namespace or is running with no_new_privs. This avoids scenarios where
461463
* unprivileged tasks can affect the behavior of privileged children.
462464
*
@@ -478,8 +480,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
478480
SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
479481
flags)
480482
{
481-
struct landlock_ruleset *new_dom,
482-
*ruleset __free(landlock_put_ruleset) = NULL;
483+
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
483484
struct cred *new_cred;
484485
struct landlock_cred_security *new_llcred;
485486
bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
@@ -538,33 +539,43 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
538539
* We could optimize this case by not calling commit_creds() if this flag
539540
* was already set, but it is not worth the complexity.
540541
*/
541-
if (!ruleset)
542-
return commit_creds(new_cred);
543-
544-
/*
545-
* There is no possible race condition while copying and manipulating
546-
* the current credentials because they are dedicated per thread.
547-
*/
548-
new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
549-
if (IS_ERR(new_dom)) {
550-
abort_creds(new_cred);
551-
return PTR_ERR(new_dom);
552-
}
542+
if (ruleset) {
543+
/*
544+
* There is no possible race condition while copying and
545+
* manipulating the current credentials because they are
546+
* dedicated per thread.
547+
*/
548+
struct landlock_ruleset *const new_dom =
549+
landlock_merge_ruleset(new_llcred->domain, ruleset);
550+
if (IS_ERR(new_dom)) {
551+
abort_creds(new_cred);
552+
return PTR_ERR(new_dom);
553+
}
553554

554555
#ifdef CONFIG_AUDIT
555-
new_dom->hierarchy->log_same_exec = log_same_exec;
556-
new_dom->hierarchy->log_new_exec = log_new_exec;
557-
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
558-
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
556+
new_dom->hierarchy->log_same_exec = log_same_exec;
557+
new_dom->hierarchy->log_new_exec = log_new_exec;
558+
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
559+
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
559560
#endif /* CONFIG_AUDIT */
560561

561-
/* Replaces the old (prepared) domain. */
562-
landlock_put_ruleset(new_llcred->domain);
563-
new_llcred->domain = new_dom;
562+
/* Replaces the old (prepared) domain. */
563+
landlock_put_ruleset(new_llcred->domain);
564+
new_llcred->domain = new_dom;
564565

565566
#ifdef CONFIG_AUDIT
566-
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
567+
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
567568
#endif /* CONFIG_AUDIT */
569+
}
570+
571+
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
572+
const int err = landlock_restrict_sibling_threads(
573+
current_cred(), new_cred);
574+
if (err) {
575+
abort_creds(new_cred);
576+
return err;
577+
}
578+
}
568579

569580
return commit_creds(new_cred);
570581
}

0 commit comments

Comments
 (0)