Linux Security Modules development
 help / color / mirror / Atom feed
From: "Mickaël Salaün" <mic@digikod.net>
To: "Günther Noack" <gnoack@google.com>,
	"Steven Rostedt" <rostedt@goodmis.org>
Cc: "Mickaël Salaün" <mic@digikod.net>,
	"Christian Brauner" <brauner@kernel.org>,
	"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@google.com>,
	"Justin Suess" <utilityemal77@gmail.com>,
	"Kees Cook" <kees@kernel.org>,
	"Masami Hiramatsu" <mhiramat@kernel.org>,
	"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
	"Matthieu Buffet" <matthieu@buffet.re>,
	"Mikhail Ivanov" <ivanov.mikhail1@huawei-partners.com>,
	"Tingmao Wang" <m@maowtm.org>,
	kernel-team@cloudflare.com,
	linux-security-module@vger.kernel.org,
	linux-trace-kernel@vger.kernel.org
Subject: [PATCH v3 02/20] landlock: Move domain query functions to domain.c
Date: Wed, 22 Jul 2026 19:11:34 +0200	[thread overview]
Message-ID: <20260722171159.2776765-3-mic@digikod.net> (raw)
In-Reply-To: <20260722171159.2776765-1-mic@digikod.net>

Grouping domain-specific code in one compilation unit reduces coupling
between domain and ruleset implementations.

Move the access-check functions that only operate on a domain (rule
lookup, layer unmasking, layer-mask init, access-mask union) from
ruleset.[ch] to domain.[ch].  They evaluate whether a domain grants a
requested access during the pathwalk and network checks and do not
modify the domain.

The merge and inherit chain stays in ruleset.c for now because it calls
the static create_ruleset() allocator; a following commit moves it once
the domain type switch eliminates that dependency.

No behavioral change.  The functions move with unchanged signatures and
bodies.

Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---

Changes since v2:
https://patch.msgid.link/20260406143717.1815792-3-mic@digikod.net
- Adapt the moved access-check helpers to the base's struct layer_masks
  (renamed from struct layer_access_masks), including the per-layer
  quiet flag.

Changes since v1:
- New patch.
---
 security/landlock/domain.c  | 162 ++++++++++++++++++++++++++++++++++++
 security/landlock/domain.h  |  38 +++++++++
 security/landlock/net.c     |   1 +
 security/landlock/ruleset.c | 150 ---------------------------------
 security/landlock/ruleset.h |  38 ---------
 5 files changed, 201 insertions(+), 188 deletions(-)

diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index c5efb7635a7b..8fb74107c2ef 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -10,11 +10,15 @@
 #include <kunit/test.h>
 #include <linux/bitops.h>
 #include <linux/bits.h>
+#include <linux/cleanup.h>
 #include <linux/cred.h>
+#include <linux/err.h>
 #include <linux/file.h>
 #include <linux/mm.h>
+#include <linux/overflow.h>
 #include <linux/path.h>
 #include <linux/pid.h>
+#include <linux/rbtree.h>
 #include <linux/refcount.h>
 #include <linux/sched.h>
 #include <linux/signal.h>
@@ -26,6 +30,7 @@
 #include "common.h"
 #include "domain.h"
 #include "id.h"
+#include "limits.h"
 #include "ruleset.h"
 
 static void free_domain(struct landlock_domain *const domain)
@@ -59,6 +64,163 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
 	}
 }
 
+/* The returned access has the same lifetime as the domain. */
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+		   const struct landlock_id id)
+{
+	const struct rb_root *root;
+	const struct rb_node *node;
+
+	root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
+				      id.type);
+	if (IS_ERR(root))
+		return NULL;
+	node = root->rb_node;
+
+	while (node) {
+		struct landlock_rule *this =
+			rb_entry(node, struct landlock_rule, node);
+
+		if (this->key.data == id.key.data)
+			return this;
+		if (this->key.data < id.key.data)
+			node = node->rb_right;
+		else
+			node = node->rb_left;
+	}
+	return NULL;
+}
+
+/**
+ * landlock_unmask_layers - Remove the access rights in @masks which are
+ *                          granted in @rule
+ *
+ * Updates the set of (per-layer) unfulfilled access rights @masks so that all
+ * the access rights granted in @rule are removed from it (because they are now
+ * fulfilled).
+ *
+ * @rule: A rule that grants a set of access rights for each layer.
+ * @masks: A matrix of unfulfilled access rights for each layer.
+ *
+ * Return: True if the request is allowed (i.e. the access rights granted all
+ * remaining unfulfilled access rights and masks has no leftover set bits).
+ */
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+			    struct layer_masks *masks)
+{
+	if (!masks)
+		return true;
+	if (!rule)
+		return false;
+
+	/*
+	 * An access is granted if, for each policy layer, at least one rule
+	 * encountered on the pathwalk grants the requested access, regardless
+	 * of its position in the layer stack.  We must then check the remaining
+	 * layers for each inode, from the first added layer to the last one.
+	 * When there are multiple requested accesses, for each policy layer,
+	 * the full set of requested accesses may not be granted by only one
+	 * rule, but by the union (binary OR) of multiple rules.  For example,
+	 * /a/b <execute> + /a <read> grants /a/b <execute + read>.
+	 *
+	 * This function is called once per matching rule during the pathwalk,
+	 * progressively clearing bits in @masks.  The overall access decision
+	 * is per-layer: access is granted iff masks->layers[l].access == 0 for
+	 * all layers l.  When two independent mechanisms can each grant access
+	 * within a layer (e.g. a path rule OR a scope exception), the
+	 * composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not
+	 * (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when
+	 * different layers grant via different mechanisms.
+	 */
+	for (size_t i = 0; i < rule->num_layers; i++) {
+		const struct landlock_layer *const layer = &rule->layers[i];
+
+		/* Clear the bits where the layer in the rule grants access. */
+		masks->layers[layer->level - 1].access &= ~layer->access;
+
+#ifdef CONFIG_AUDIT
+		/* Collect rule flags for each layer. */
+		if (layer->flags.quiet)
+			masks->layers[layer->level - 1].quiet = true;
+#endif /* CONFIG_AUDIT */
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+		if (masks->layers[i].access)
+			return false;
+	}
+	return true;
+}
+
+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+		  const u16 layer_level);
+
+/**
+ * landlock_init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @masks such that for each access right in @access_request, the bits
+ * for all the layers are set where this access right is handled.  Rule flags
+ * are also zeroed.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @masks: Layer access masks to populate.
+ * @key_type: The key type to switch between access masks of different types.
+ *
+ * Return: An access mask where each access right bit is set which is handled in
+ * any of the active layers in @domain.
+ */
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+			  const access_mask_t access_request,
+			  struct layer_masks *const masks,
+			  const enum landlock_key_type key_type)
+{
+	access_mask_t handled_accesses = 0;
+	get_access_mask_t *get_access_mask;
+
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		get_access_mask = landlock_get_fs_access_mask;
+		break;
+
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		get_access_mask = landlock_get_net_access_mask;
+		break;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+	default:
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
+	if (!access_request)
+		return 0;
+
+	for (size_t i = 0; i < domain->num_layers; i++) {
+		const access_mask_t handled = get_access_mask(domain, i);
+
+		masks->layers[i].access = access_request & handled;
+		handled_accesses |= masks->layers[i].access;
+#ifdef CONFIG_AUDIT
+		masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
+	}
+	for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
+	     i++) {
+		masks->layers[i].access = 0;
+#ifdef CONFIG_AUDIT
+		masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
+	}
+
+	return handled_accesses;
+}
+
 #ifdef CONFIG_AUDIT
 
 /**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index bcfd0452e260..9f9b892ab17d 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -233,12 +233,50 @@ struct landlock_domain {
 	};
 };
 
+/**
+ * landlock_union_access_masks - Return all access rights handled in the
+ *				 domain
+ *
+ * @domain: Landlock ruleset (used as a domain)
+ *
+ * Return: An access_masks result of the OR of all the domain's access masks.
+ */
+static inline struct access_masks
+landlock_union_access_masks(const struct landlock_ruleset *const domain)
+{
+	union access_masks_all matches = {};
+	size_t layer_level;
+
+	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+		union access_masks_all layer = {
+			.masks = domain->access_masks[layer_level],
+		};
+
+		matches.all |= layer.all;
+	}
+
+	return matches.masks;
+}
+
 void landlock_put_domain(struct landlock_domain *const domain);
 void landlock_put_domain_deferred(struct landlock_domain *const domain);
 
 DEFINE_FREE(landlock_put_domain, struct landlock_domain *,
 	    if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T))
 
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+		   const struct landlock_id id);
+
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+			    struct layer_masks *masks);
+
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+			  const access_mask_t access_request,
+			  struct layer_masks *masks,
+			  const enum landlock_key_type key_type);
+
 static inline void landlock_get_domain(struct landlock_domain *const domain)
 {
 	if (domain)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 46c17116fcf4..feecc8c77ab4 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -15,6 +15,7 @@
 #include "audit.h"
 #include "common.h"
 #include "cred.h"
+#include "domain.h"
 #include "limits.h"
 #include "net.h"
 #include "ruleset.h"
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 9e560aed64b6..96e65804d2ff 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -591,153 +591,3 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
 
 	return no_free_ptr(new_dom);
 }
-
-/*
- * The returned access has the same lifetime as @ruleset.
- */
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_id id)
-{
-	const struct rb_root *root;
-	const struct rb_node *node;
-
-	root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
-				      id.type);
-	if (IS_ERR(root))
-		return NULL;
-	node = root->rb_node;
-
-	while (node) {
-		struct landlock_rule *this =
-			rb_entry(node, struct landlock_rule, node);
-
-		if (this->key.data == id.key.data)
-			return this;
-		if (this->key.data < id.key.data)
-			node = node->rb_right;
-		else
-			node = node->rb_left;
-	}
-	return NULL;
-}
-
-/**
- * landlock_unmask_layers - Remove the access rights in @masks
- *                          which are granted in @rule
- *
- * Updates the set of (per-layer) unfulfilled access rights @masks
- * so that all the access rights granted in @rule are removed from it
- * (because they are now fulfilled).
- *
- * @rule: A rule that grants a set of access rights for each layer
- * @masks: A matrix of unfulfilled access rights for each layer
- *
- * Return: True if the request is allowed (i.e. the access rights granted all
- * remaining unfulfilled access rights and masks has no leftover set bits).
- */
-bool landlock_unmask_layers(const struct landlock_rule *const rule,
-			    struct layer_masks *masks)
-{
-	if (!masks)
-		return true;
-	if (!rule)
-		return false;
-
-	/*
-	 * An access is granted if, for each policy layer, at least one rule
-	 * encountered on the pathwalk grants the requested access,
-	 * regardless of its position in the layer stack.  We must then check
-	 * the remaining layers for each inode, from the first added layer to
-	 * the last one.  When there is multiple requested accesses, for each
-	 * policy layer, the full set of requested accesses may not be granted
-	 * by only one rule, but by the union (binary OR) of multiple rules.
-	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
-	 */
-	for (size_t i = 0; i < rule->num_layers; i++) {
-		const struct landlock_layer *const layer = &rule->layers[i];
-
-		/* Clear the bits where the layer in the rule grants access. */
-		masks->layers[layer->level - 1].access &= ~layer->access;
-
-#ifdef CONFIG_AUDIT
-		/* Collect rule flags for each layer. */
-		if (layer->flags.quiet)
-			masks->layers[layer->level - 1].quiet = true;
-#endif /* CONFIG_AUDIT */
-	}
-
-	for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
-		if (masks->layers[i].access)
-			return false;
-	}
-	return true;
-}
-
-typedef access_mask_t
-get_access_mask_t(const struct landlock_ruleset *const ruleset,
-		  const u16 layer_level);
-
-/**
- * landlock_init_layer_masks - Initialize layer masks from an access request
- *
- * Populates @masks such that for each access right in @access_request, the bits
- * for all the layers are set where this access right is handled.  Rule flags
- * are also zeroed.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @masks: Layer access masks to populate.
- * @key_type: The key type to switch between access masks of different types.
- *
- * Return: An access mask where each access right bit is set which is handled
- * in any of the active layers in @domain.
- */
-access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
-			  const access_mask_t access_request,
-			  struct layer_masks *const masks,
-			  const enum landlock_key_type key_type)
-{
-	access_mask_t handled_accesses = 0;
-	get_access_mask_t *get_access_mask;
-
-	switch (key_type) {
-	case LANDLOCK_KEY_INODE:
-		get_access_mask = landlock_get_fs_access_mask;
-		break;
-
-#if IS_ENABLED(CONFIG_INET)
-	case LANDLOCK_KEY_NET_PORT:
-		get_access_mask = landlock_get_net_access_mask;
-		break;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
-	default:
-		WARN_ON_ONCE(1);
-		return 0;
-	}
-
-	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
-	if (!access_request)
-		return 0;
-
-	for (size_t i = 0; i < domain->num_layers; i++) {
-		const access_mask_t handled = get_access_mask(domain, i);
-
-		masks->layers[i].access = access_request & handled;
-		handled_accesses |= masks->layers[i].access;
-#ifdef CONFIG_AUDIT
-		masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
-	}
-	for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
-	     i++) {
-		masks->layers[i].access = 0;
-#ifdef CONFIG_AUDIT
-		masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
-	}
-
-	return handled_accesses;
-}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index c8c83efff4c5..f9486c06b7ec 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -235,10 +235,6 @@ struct landlock_ruleset *
 landlock_merge_ruleset(struct landlock_ruleset *const parent,
 		       struct landlock_ruleset *const ruleset);
 
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_id id);
-
 /**
  * landlock_get_rule_root - Get the root of a rule tree by key type
  *
@@ -272,31 +268,6 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 		refcount_inc(&ruleset->usage);
 }
 
-/**
- * landlock_union_access_masks - Return all access rights handled in the
- *				 domain
- *
- * @domain: Landlock ruleset (used as a domain)
- *
- * Return: An access_masks result of the OR of all the domain's access masks.
- */
-static inline struct access_masks
-landlock_union_access_masks(const struct landlock_ruleset *const domain)
-{
-	union access_masks_all matches = {};
-	size_t layer_level;
-
-	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
-		union access_masks_all layer = {
-			.masks = domain->access_masks[layer_level],
-		};
-
-		matches.all |= layer.all;
-	}
-
-	return matches.masks;
-}
-
 static inline void
 landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 			    const access_mask_t fs_access_mask,
@@ -355,13 +326,4 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
 	return ruleset->access_masks[layer_level].scope;
 }
 
-bool landlock_unmask_layers(const struct landlock_rule *const rule,
-			    struct layer_masks *masks);
-
-access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
-			  const access_mask_t access_request,
-			  struct layer_masks *masks,
-			  const enum landlock_key_type key_type);
-
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
-- 
2.54.0


  parent reply	other threads:[~2026-07-22 17:12 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-22 17:11 [PATCH v3 00/20] Landlock tracepoints Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 01/20] landlock: Prepare ruleset and domain type split Mickaël Salaün
2026-07-22 17:11 ` Mickaël Salaün [this message]
2026-07-22 17:11 ` [PATCH v3 03/20] landlock: Split struct landlock_domain from struct landlock_ruleset Mickaël Salaün
2026-07-22 21:12   ` Justin Suess
2026-07-22 17:11 ` [PATCH v3 04/20] landlock: Split denial logging from audit into common framework Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 05/20] landlock: Decouple the per-denial logging decision from CONFIG_AUDIT Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 06/20] landlock: Consolidate access-right and scope names in a shared header Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 07/20] tracing: Add __print_untrusted_str() Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 08/20] landlock: Add create_ruleset and free_ruleset tracepoints Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 09/20] landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 10/20] landlock: Add create_domain and free_domain tracepoints Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 11/20] landlock: Add landlock_enforce_domain tracepoint Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 12/20] landlock: Add tracepoints for rule checking Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 13/20] landlock: Add landlock_deny_access_fs and landlock_deny_access_net Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 14/20] landlock: Add tracepoints for ptrace and scope denials Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 15/20] selftests/landlock: Add trace event test infrastructure and tests Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 16/20] selftests/landlock: Add filesystem tracepoint tests Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 17/20] selftests/landlock: Add network " Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 18/20] selftests/landlock: Add scope and ptrace " Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 19/20] selftests/landlock: Add landlock_enforce_domain trace tests Mickaël Salaün
2026-07-22 17:11 ` [PATCH v3 20/20] landlock: Document tracepoints Mickaël Salaün

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=20260722171159.2776765-3-mic@digikod.net \
    --to=mic@digikod.net \
    --cc=brauner@kernel.org \
    --cc=gnoack@google.com \
    --cc=ivanov.mikhail1@huawei-partners.com \
    --cc=jannh@google.com \
    --cc=jeffxu@google.com \
    --cc=kees@kernel.org \
    --cc=kernel-team@cloudflare.com \
    --cc=linux-security-module@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=m@maowtm.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=matthieu@buffet.re \
    --cc=mhiramat@kernel.org \
    --cc=rostedt@goodmis.org \
    --cc=utilityemal77@gmail.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