mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
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: https://github.com/landlock-lsm/linux/issues/2 Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
@@ -117,11 +117,24 @@ struct landlock_ruleset_attr {
|
||||
* future nested domains, not the one being created. It can also be used
|
||||
* with a @ruleset_fd value of -1 to mute subdomain logs without creating a
|
||||
* domain.
|
||||
*
|
||||
* The following flag supports policy enforcement in multithreaded processes:
|
||||
*
|
||||
* %LANDLOCK_RESTRICT_SELF_TSYNC
|
||||
* Applies the new Landlock configuration atomically to all threads of the
|
||||
* current process, including the Landlock domain and logging
|
||||
* configuration. This overrides the Landlock configuration of sibling
|
||||
* threads, irrespective of previously established Landlock domains and
|
||||
* logging configurations on these threads.
|
||||
*
|
||||
* If the calling thread is running with no_new_privs, this operation
|
||||
* enables no_new_privs on the sibling threads as well.
|
||||
*/
|
||||
/* clang-format off */
|
||||
#define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0)
|
||||
#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
|
||||
#define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2)
|
||||
#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
|
||||
|
||||
landlock-y := setup.o syscalls.o object.o ruleset.o \
|
||||
cred.o task.o fs.o
|
||||
landlock-y := \
|
||||
setup.o \
|
||||
syscalls.o \
|
||||
object.o \
|
||||
ruleset.o \
|
||||
cred.o \
|
||||
task.o \
|
||||
fs.o \
|
||||
tsync.o
|
||||
|
||||
landlock-$(CONFIG_INET) += net.o
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
* This structure is packed to minimize the size of struct
|
||||
* landlock_file_security. However, it is always aligned in the LSM cred blob,
|
||||
* see lsm_set_blob_size().
|
||||
*
|
||||
* When updating this, also update landlock_cred_copy() if needed.
|
||||
*/
|
||||
struct landlock_cred_security {
|
||||
/**
|
||||
@@ -65,6 +67,16 @@ landlock_cred(const struct cred *cred)
|
||||
return cred->security + landlock_blob_sizes.lbs_cred;
|
||||
}
|
||||
|
||||
static inline void landlock_cred_copy(struct landlock_cred_security *dst,
|
||||
const struct landlock_cred_security *src)
|
||||
{
|
||||
landlock_put_ruleset(dst->domain);
|
||||
|
||||
*dst = *src;
|
||||
|
||||
landlock_get_ruleset(src->domain);
|
||||
}
|
||||
|
||||
static inline struct landlock_ruleset *landlock_get_current_domain(void)
|
||||
{
|
||||
return landlock_cred(current_cred())->domain;
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
|
||||
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
|
||||
|
||||
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
|
||||
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
|
||||
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "net.h"
|
||||
#include "ruleset.h"
|
||||
#include "setup.h"
|
||||
#include "tsync.h"
|
||||
|
||||
static bool is_initialized(void)
|
||||
{
|
||||
@@ -161,7 +162,7 @@ static const struct file_operations ruleset_fops = {
|
||||
* Documentation/userspace-api/landlock.rst should be updated to reflect the
|
||||
* UAPI change.
|
||||
*/
|
||||
const int landlock_abi_version = 7;
|
||||
const int landlock_abi_version = 8;
|
||||
|
||||
/**
|
||||
* sys_landlock_create_ruleset - Create a new ruleset
|
||||
@@ -454,9 +455,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
||||
* - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
|
||||
* - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
|
||||
* - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
|
||||
* - %LANDLOCK_RESTRICT_SELF_TSYNC
|
||||
*
|
||||
* This system call enables to enforce a Landlock ruleset on the current
|
||||
* thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
|
||||
* This system call enforces a Landlock ruleset on the current thread.
|
||||
* Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
|
||||
* namespace or is running with no_new_privs. This avoids scenarios where
|
||||
* unprivileged tasks can affect the behavior of privileged children.
|
||||
*
|
||||
@@ -478,8 +480,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
||||
SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
|
||||
flags)
|
||||
{
|
||||
struct landlock_ruleset *new_dom,
|
||||
*ruleset __free(landlock_put_ruleset) = NULL;
|
||||
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
|
||||
struct cred *new_cred;
|
||||
struct landlock_cred_security *new_llcred;
|
||||
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,
|
||||
* We could optimize this case by not calling commit_creds() if this flag
|
||||
* was already set, but it is not worth the complexity.
|
||||
*/
|
||||
if (!ruleset)
|
||||
return commit_creds(new_cred);
|
||||
if (ruleset) {
|
||||
/*
|
||||
* There is no possible race condition while copying and
|
||||
* manipulating the current credentials because they are
|
||||
* dedicated per thread.
|
||||
*/
|
||||
struct landlock_ruleset *const new_dom =
|
||||
landlock_merge_ruleset(new_llcred->domain, ruleset);
|
||||
if (IS_ERR(new_dom)) {
|
||||
abort_creds(new_cred);
|
||||
return PTR_ERR(new_dom);
|
||||
}
|
||||
|
||||
/*
|
||||
* There is no possible race condition while copying and manipulating
|
||||
* the current credentials because they are dedicated per thread.
|
||||
*/
|
||||
new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
|
||||
if (IS_ERR(new_dom)) {
|
||||
abort_creds(new_cred);
|
||||
return PTR_ERR(new_dom);
|
||||
#ifdef CONFIG_AUDIT
|
||||
new_dom->hierarchy->log_same_exec = log_same_exec;
|
||||
new_dom->hierarchy->log_new_exec = log_new_exec;
|
||||
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
|
||||
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
|
||||
/* Replaces the old (prepared) domain. */
|
||||
landlock_put_ruleset(new_llcred->domain);
|
||||
new_llcred->domain = new_dom;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
|
||||
#endif /* CONFIG_AUDIT */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
new_dom->hierarchy->log_same_exec = log_same_exec;
|
||||
new_dom->hierarchy->log_new_exec = log_new_exec;
|
||||
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
|
||||
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
|
||||
/* Replaces the old (prepared) domain. */
|
||||
landlock_put_ruleset(new_llcred->domain);
|
||||
new_llcred->domain = new_dom;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
|
||||
#endif /* CONFIG_AUDIT */
|
||||
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
|
||||
const int err = landlock_restrict_sibling_threads(
|
||||
current_cred(), new_cred);
|
||||
if (err) {
|
||||
abort_creds(new_cred);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return commit_creds(new_cred);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Landlock - Cross-thread ruleset enforcement
|
||||
*
|
||||
* Copyright © 2025 Google LLC
|
||||
*/
|
||||
|
||||
#ifndef _SECURITY_LANDLOCK_TSYNC_H
|
||||
#define _SECURITY_LANDLOCK_TSYNC_H
|
||||
|
||||
#include <linux/cred.h>
|
||||
|
||||
int landlock_restrict_sibling_threads(const struct cred *old_cred,
|
||||
const struct cred *new_cred);
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_TSYNC_H */
|
||||
@@ -76,7 +76,7 @@ TEST(abi_version)
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
};
|
||||
ASSERT_EQ(7, landlock_create_ruleset(NULL, 0,
|
||||
ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
|
||||
@@ -306,7 +306,7 @@ TEST(restrict_self_fd_flags)
|
||||
|
||||
TEST(restrict_self_flags)
|
||||
{
|
||||
const __u32 last_flag = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
|
||||
const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;
|
||||
|
||||
/* Tests invalid flag combinations. */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user