[PATCH v5 07/24] landlock: Prepare to use credential instead of domain for fowner

Mickaël Salaün
2025-01-31T16:31:26Z

This cosmetic change that is needed for audit support, specifically to
be able to filter according to cross-execution boundaries.

struct landlock_file_security's size stay the same for now but it will
increase with struct landlock_cred_security's size.

Only save Landlock domain in hook_file_set_fowner() if the current
domain has LANDLOCK_SCOPE_SIGNAL, which was previously done for each
hook_file_send_sigiotask() calls. This should improve a bit
performances.

Replace hardcoded LANDLOCK_SCOPE_SIGNAL with the signal_scope.scope
variable.

Use scoped guards for RCU read-side critical sections.

Cc: Günther Noack <gnoack@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20250131163059.1139617-8-mic@digikod.net
---

Changes since v4:
- New patch.
---
security/landlock/fs.c | 26 ++++++++++++++++++++------
security/landlock/fs.h | 12 +++++++-----
security/landlock/task.c | 25 ++++++++++++++++---------
3 files changed, 43 insertions(+), 20 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index d5b153d29fcb..276cbcffe6f5 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1641,17 +1641,31 @@ 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;
+ static const struct access_masks signal_scope = {
+ .scope = LANDLOCK_SCOPE_SIGNAL,
+ };
+ const struct landlock_cred_security *new_subject;
+ struct landlock_cred_security *fown_subject;
+ struct landlock_ruleset *prev_dom;

/*
* 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);
- prev_dom = landlock_file(file)->fown_domain;
- landlock_file(file)->fown_domain = new_dom;
+
+ fown_subject = &landlock_file(file)->fown_subject;
+ prev_dom = fown_subject->domain;
+ new_subject = landlock_get_applicable_subject(current_cred(),
+ signal_scope, NULL);
+ if (new_subject) {
+ *fown_subject = *new_subject;
+ } else {
+ static const struct landlock_cred_security empty = {};
+
+ *fown_subject = empty;
+ }
+ landlock_get_ruleset(fown_subject->domain);

/* Called in an RCU read-side critical section. */
landlock_put_ruleset_deferred(prev_dom);
@@ -1659,7 +1673,7 @@ static void hook_file_set_fowner(struct file *file)

static void hook_file_free_security(struct file *file)
{
- landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
+ landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
}

static struct security_hook_list landlock_hooks[] __ro_after_init = {
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index d445f411c26a..1449a90e92c7 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -14,6 +14,7 @@
#include <linux/rcupdate.h>

#include "access.h"
+#include "cred.h"
#include "ruleset.h"
#include "setup.h"

@@ -54,12 +55,13 @@ struct landlock_file_security {
*/
access_mask_t allowed_access;
/**
- * @fown_domain: Domain of the task that set the PID that may receive a
- * signal e.g., SIGURG when writing MSG_OOB to the related socket.
- * This pointer is protected by the related file->f_owner->lock, as for
- * fown_struct's members: pid, uid, and euid.
+ * @fown_subject: Landlock credential of the task that set the PID that
+ * may receive a signal e.g., SIGURG when writing MSG_OOB to the
+ * related socket. This pointer is protected by the related
+ * file->f_owner->lock, as for fown_struct's members: pid, uid, and
+ * euid.
*/
- struct landlock_ruleset *fown_domain;
+ struct landlock_cred_security fown_subject;
};

/**
diff --git a/security/landlock/task.c b/security/landlock/task.c
index dbdfac11e015..da8f82c8054a 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -288,22 +288,29 @@ static int hook_task_kill(struct task_struct *const p,
static int hook_file_send_sigiotask(struct task_struct *tsk,
struct fown_struct *fown, int signum)
{
- const struct landlock_ruleset *dom;
+ const struct landlock_cred_security *subject;
bool is_scoped = false;

/* Lock already held by send_sigio() and send_sigurg(). */
lockdep_assert_held(&fown->lock);
- dom = landlock_get_applicable_domain(
- landlock_file(fown->file)->fown_domain, signal_scope);
+ subject = &landlock_file(fown->file)->fown_subject;

- /* Quick return for unowned socket. */
- if (!dom)
+ /*
+ * Quick return for unowned socket.
+ *
+ * subject->domain has already been filtered when saved by
+ * hook_file_set_fowner(), so there is no need to call
+ * landlock_get_applicable_subject() here.
+ */
+ if (!subject->domain)
return 0;

- rcu_read_lock();
- is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
- LANDLOCK_SCOPE_SIGNAL);
- rcu_read_unlock();
+ scoped_guard(rcu)
+ {
+ is_scoped = domain_is_scoped(subject->domain,
+ landlock_get_task_domain(tsk),
+ signal_scope.scope);
+ }
if (is_scoped)
return -EPERM;

--
2.48.1