[RFC PATCH v2 02/12] landlock/domain: Define structure and macros for flat-array domains

Tingmao Wang m at maowtm.org
Sun Jul 6 15:16:43 UTC 2025


This implements the structure proposed in in [1], using a flat array to
store the rules and eventually using hashing to find rules.  The array is
stored in the domain struct itself to avoid extra pointer indirection and
make all the rule data as cache-local as possible.  The non-array part of
the domain struct is also kept reasonably small.  This works well for a
small (10 or 20 rules) ruleset, which is the common case for Landlock
users, and still has reasonable performance for large ones.

This will eventually make landlock_rule/landlock_ruleset only needed for
unmerged rulesets, and thus it doesn't have to store multiple layers etc.
create_rule and insert_rule would also hopefully become much simpler.

Different to the original proposal, the number of layers for each rule is
now deducted from the layer index of the next offset.  In order to
simplify logic, a special "terminating index" is placed after each of the
two index arrays, which will contain a layer_index = num_layers.

On reflection, using the name "layer" to refer to individual struct
landlock_layers is very confusing especially with names like num_layers -
the next version should probably find a better name for it.

Link: https://lore.kernel.org/all/20250526.quec3Dohsheu@digikod.net/ [1]

Signed-off-by: Tingmao Wang <m at maowtm.org>
---
 security/landlock/domain.c |   8 +++
 security/landlock/domain.h | 125 +++++++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+)

diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index a647b68e8d06..83233bf3be6a 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -24,6 +24,14 @@
 #include "domain.h"
 #include "id.h"
 
+static void __maybe_unused build_check_domain(void)
+{
+	BUILD_BUG_ON(LANDLOCK_MAX_NUM_RULES >= U32_MAX - 1);
+	/* Non-inclusive end indices are involved, so needs to be U32_MAX - 1. */
+	BUILD_BUG_ON(LANDLOCK_MAX_NUM_RULES * LANDLOCK_MAX_NUM_LAYERS >=
+		     U32_MAX - 1);
+}
+
 #ifdef CONFIG_AUDIT
 
 /**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 7fb70b25f85a..b0f5ba59ff4c 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -5,6 +5,7 @@
  * Copyright © 2016-2020 Mickaël Salaün <mic at digikod.net>
  * Copyright © 2018-2020 ANSSI
  * Copyright © 2024-2025 Microsoft Corporation
+ * Copyright © 2025      Tingmao Wang <m at maowtm.org>
  */
 
 #ifndef _SECURITY_LANDLOCK_DOMAIN_H
@@ -20,6 +21,130 @@
 
 #include "access.h"
 #include "audit.h"
+#include "ruleset.h"
+
+struct landlock_domain_index {
+	/**
+	 * @key: The landlock object or port identifier.
+	 */
+	union landlock_key key;
+	/**
+	 * @next_collision: Points to another slot in the domain indices
+	 * array, forming a collision chain in a coalesced hashtable.  See
+	 * landlock_domain_find for how this is used.
+	 */
+	u32 next_collision;
+	/**
+	 * @layer_index: The index of the first landlock_layer corresponding
+	 * to this key in the relevant subarray.  A rule may have multiple
+	 * layers.  The end of the layers region for this rule is the index of
+	 * the next struct landlock_domain_index in the array.
+	 */
+	u32 layer_index;
+};
+
+struct landlock_domain {
+	/**
+	 * @num_layers: Number of layers in this domain.  This enables to
+	 * check that all the layers allow an access request.
+	 */
+	u32 num_layers;
+	/**
+	 * @num_fs_indices: Number of non-overlapping (i.e. not for the same
+	 * object) inode rules.  Does not include the terminating index.
+	 */
+	u32 num_fs_indices;
+	/**
+	 * @num_net_indices: Number of non-overlapping (i.e. not for the same
+	 * port) network rules.  Does not include the terminating index.
+	 */
+	u32 num_net_indices;
+	/**
+	 * @num_fs_layers: Number of landlock_layer in the fs_layers array.
+	 */
+	u32 num_fs_layers;
+	/**
+	 * @num_net_layers: Number of landlock_layer in the net_layers array.
+	 */
+	u32 num_net_layers;
+	/**
+	 * @len_rules: Total length (in units of uintptr_t) of the rules
+	 * array.  Used to check accesses are not out of bounds, but in theory
+	 * this is always derivable from the other length fields.
+	 */
+	u32 len_rules;
+	/**
+	 * @rules: The rest of this struct consists of 5 dynamically-sized,
+	 * arrays as well as 2 terminating indices, placed one after another,
+	 * the contents of which are to be accessed with dom_ helper macros
+	 * defined in this header.  They are:
+	 *
+	 *     struct access_masks access_masks[num_layers];
+	 *     (possible alignment padding here)
+	 *     struct landlock_domain_index fs_indices[num_fs_indices];
+	 *     struct landlock_domain_index terminating_fs_index;
+	 *     struct landlock_domain_index net_indices[num_net_indices];
+	 *     struct landlock_domain_index terminating_net_index;
+	 *     struct landlock_layer fs_layers[num_fs_layers];
+	 *     struct landlock_layer net_layers[num_net_layers];
+	 *     (possible alignment padding here)
+	 *
+	 * The purpose of the terminating indices is to allow getting the
+	 * non-inclusive end index of the layers for a rule without branching.
+	 * They do not represent any rules themselves, and the only valid
+	 * field for those two indices is layer_index.
+	 */
+	uintptr_t rules[] __counted_by(len_rules);
+};
+
+#define dom_access_masks(dom) ((struct access_masks *)((dom)->rules))
+
+#define _dom_fs_indices_offset(dom)                                        \
+	(ALIGN(array_size((dom)->num_layers, sizeof(struct access_masks)), \
+	       sizeof(uintptr_t)))
+
+#define dom_fs_indices(dom)                                      \
+	((struct landlock_domain_index *)((char *)(dom)->rules + \
+					  _dom_fs_indices_offset(dom)))
+
+#define dom_fs_terminating_index(dom) \
+	(&dom_fs_indices(dom)[(dom)->num_fs_indices])
+
+#define _dom_net_indices_offset(dom)                       \
+	(_dom_fs_indices_offset(dom) +                     \
+	 array_size(((size_t)(dom)->num_fs_indices) + 1ul, \
+		    sizeof(struct landlock_domain_index)))
+
+#define dom_net_indices(dom)                                     \
+	((struct landlock_domain_index *)((char *)(dom)->rules + \
+					  _dom_net_indices_offset(dom)))
+
+#define dom_net_terminating_index(dom) \
+	(&dom_net_indices(dom)[(dom)->num_net_indices])
+
+#define _dom_fs_layers_offset(dom)                          \
+	(_dom_net_indices_offset(dom) +                     \
+	 array_size((size_t)((dom)->num_net_indices) + 1ul, \
+		    sizeof(struct landlock_domain_index)))
+
+#define dom_fs_layers(dom)                                \
+	((struct landlock_layer *)((char *)(dom)->rules + \
+				   _dom_fs_layers_offset(dom)))
+
+#define _dom_net_layers_offset(dom)   \
+	(_dom_fs_layers_offset(dom) + \
+	 array_size((dom)->num_fs_layers, sizeof(struct landlock_layer)))
+
+#define dom_net_layers(dom)                               \
+	((struct landlock_layer *)((char *)(dom)->rules + \
+				   _dom_net_layers_offset(dom)))
+
+#define dom_rules_len(dom)                                        \
+	(ALIGN(_dom_net_layers_offset(dom) +                      \
+		       array_size((dom)->num_net_layers,          \
+				  sizeof(struct landlock_layer)), \
+	       sizeof(uintptr_t)) /                               \
+	 sizeof(uintptr_t))
 
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
-- 
2.49.0




More information about the Linux-security-module-archive mailing list