[RFC PATCH v2 08/12] landlock/domain: Add landlock_domain_merge_ruleset

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


Implement the equivalent of landlock_merge_ruleset, but using the new
domain structure.  The logic in inherit_domain and
landlock_domain_merge_ruleset c.f. inherit_ruleset and merge_ruleset.
Once we replace the existing landlock_restrict_self code to use this those
two functions can then be removed.

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

diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index a7474b803c47..da8f1bf00db1 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -147,7 +147,7 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
  * @out_num_layers: Outputs the number of layer objects that would be
  * needed in the new domain.
  */
-static int __maybe_unused dom_calculate_merged_sizes(
+static int dom_calculate_merged_sizes(
 	const struct landlock_domain_index *const dom_ind_array,
 	const u32 dom_num_indices, const u32 dom_num_layers,
 	const struct rb_root *const child_rules, u32 *const out_num_indices,
@@ -202,7 +202,7 @@ static int __maybe_unused dom_calculate_merged_sizes(
  * @out_indices: The output indices array to be populated.
  * @out_size: The size of @out_indices, in number of elements.
  */
-static int __maybe_unused dom_populate_indices(
+static int dom_populate_indices(
 	const struct landlock_domain_index *const dom_ind_array,
 	const u32 dom_num_indices, const struct rb_root *const child_rules,
 	struct landlock_domain_index *const out_indices, const u32 out_size)
@@ -344,6 +344,245 @@ dom_populate_layers(const struct landlock_domain_index *const dom_ind_array,
 	return 0;
 }
 
+/**
+ * merge_domain - Do merge for both fs and net.
+ *
+ * @parent: Parent domain, or NULL if there is no parent.
+ * @child: Child domain.  child->num_layers must be set to the new level.
+ * @ruleset: Ruleset to be merged.  Must hold the ruleset lock across
+ * calls to this function.
+ * @only_calc_sizes: Whether this is a size-calculation pass, or the final
+ * merge pass.  For the size-calculation pass, no data except various sizes
+ * in @child will be written.
+ *
+ * If @only_calc_sizes is true, child->num_{fs,net}_{indices,layers} will
+ * be updated.  Otherwise, the function writes to child->rules and checks
+ * that the number of indices and layers written matches with previously
+ * stored numbers in @child.
+ */
+static int merge_domain(const struct landlock_domain *parent,
+			struct landlock_domain *child,
+			struct landlock_ruleset *ruleset, bool only_calc_sizes)
+	__must_hold(&ruleset->lock)
+{
+	u32 new_level;
+	int err;
+
+	if (WARN_ON_ONCE(!ruleset || !child))
+		return -EINVAL;
+
+	new_level = child->num_layers;
+	/* We should have checked new_level <= LANDLOCK_MAX_NUM_LAYERS already */
+	if (WARN_ON_ONCE(new_level == 0 || new_level > LANDLOCK_MAX_NUM_LAYERS))
+		return -EINVAL;
+
+	if (only_calc_sizes) {
+		err = dom_calculate_merged_sizes(
+			parent ? dom_fs_indices(parent) : NULL,
+			parent ? parent->num_fs_indices : 0,
+			parent ? parent->num_fs_layers : 0,
+			&ruleset->root_inode, &child->num_fs_indices,
+			&child->num_fs_layers);
+		if (err)
+			return err;
+
+#ifdef CONFIG_INET
+		err = dom_calculate_merged_sizes(
+			parent ? dom_net_indices(parent) : NULL,
+			parent ? parent->num_net_indices : 0,
+			parent ? parent->num_net_layers : 0,
+			&ruleset->root_net_port, &child->num_net_indices,
+			&child->num_net_layers);
+		if (err)
+			return err;
+#else
+		child->num_net_indices = 0;
+		child->num_net_layers = 0;
+#endif /* CONFIG_INET */
+	} else {
+		err = dom_populate_indices(
+			parent ? dom_fs_indices(parent) : NULL,
+			parent ? parent->num_fs_indices : 0,
+			&ruleset->root_inode, dom_fs_indices(child),
+			child->num_fs_indices);
+		if (err)
+			return err;
+
+		err = dom_populate_layers(
+			parent ? dom_fs_indices(parent) : NULL,
+			parent ? parent->num_fs_indices : 0,
+			parent ? dom_fs_layers(parent) : NULL,
+			parent ? parent->num_fs_layers : 0,
+			&ruleset->root_inode, dom_fs_indices(child),
+			child->num_fs_indices, new_level, dom_fs_layers(child),
+			child->num_fs_layers);
+		if (err)
+			return err;
+
+		dom_fs_terminating_index(child)->layer_index =
+			child->num_fs_layers;
+
+#ifdef CONFIG_INET
+		err = dom_populate_indices(
+			parent ? dom_net_indices(parent) : NULL,
+			parent ? parent->num_net_indices : 0,
+			&ruleset->root_net_port, dom_net_indices(child),
+			child->num_net_indices);
+		if (err)
+			return err;
+
+		err = dom_populate_layers(
+			parent ? dom_net_indices(parent) : NULL,
+			parent ? parent->num_net_indices : 0,
+			parent ? dom_net_layers(parent) : NULL,
+			parent ? parent->num_net_layers : 0,
+			&ruleset->root_net_port, dom_net_indices(child),
+			child->num_net_indices, new_level,
+			dom_net_layers(child), child->num_net_layers);
+		if (err)
+			return err;
+
+		dom_net_terminating_index(child)->layer_index =
+			child->num_net_layers;
+#endif /* CONFIG_INET */
+	}
+
+	return 0;
+}
+
+/**
+ * Populate handled access masks and hierarchy for the child.
+ *
+ * @parent: Parent domain, or NULL if there is no parent.
+ * @child: Child domain to be populated.
+ * @ruleset: Ruleset to be merged.  Must hold the ruleset lock.
+ */
+static int inherit_domain(const struct landlock_domain *parent,
+			  struct landlock_domain *child,
+			  struct landlock_ruleset *ruleset)
+	__must_hold(&ruleset->lock)
+{
+	if (WARN_ON_ONCE(!child || !ruleset || !child->hierarchy ||
+			 child->num_layers < 1 || ruleset->num_layers != 1)) {
+		return -EINVAL;
+	}
+
+	if (parent) {
+		if (WARN_ON_ONCE(child->num_layers != parent->num_layers + 1))
+			return -EINVAL;
+
+		/* Copies the parent layer stack. */
+		memcpy(dom_access_masks(child), dom_access_masks(parent),
+		       array_size(parent->num_layers,
+				  sizeof(*dom_access_masks(child))));
+
+		if (WARN_ON_ONCE(!parent->hierarchy))
+			return -EINVAL;
+
+		landlock_get_hierarchy(parent->hierarchy);
+		child->hierarchy->parent = parent->hierarchy;
+	}
+
+	/* Stacks the new layer. */
+	dom_access_masks(child)[child->num_layers - 1] =
+		landlock_upgrade_handled_access_masks(ruleset->access_masks[0]);
+
+	return 0;
+}
+
+/**
+ * landlock_domain_merge_ruleset - Merge a ruleset and a parent domain
+ * into a new domain.
+ *
+ * @parent: Parent domain.
+ * @ruleset: Ruleset to be merged.  This function will take the mutex on
+ * this ruleset while merging.
+ *
+ * The current task is requesting to be restricted.  The subjective credentials
+ * must not be in an overridden state. cf. landlock_init_hierarchy_log().
+ *
+ * Returns the intersection of @parent and @ruleset, or returns @parent if
+ * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty.
+ */
+struct landlock_domain *
+landlock_domain_merge_ruleset(const struct landlock_domain *parent,
+			      struct landlock_ruleset *ruleset)
+{
+	struct landlock_domain *new_dom __free(landlock_put_domain) = NULL;
+	struct landlock_hierarchy *new_hierarchy __free(kfree) = NULL;
+	struct landlock_domain new_dom_sizes = {};
+	u32 new_level;
+	int err;
+
+	might_sleep();
+	if (WARN_ON_ONCE(!ruleset))
+		return ERR_PTR(-EINVAL);
+
+	if (parent) {
+		if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
+			return ERR_PTR(-E2BIG);
+		new_level = parent->num_layers + 1;
+	} else {
+		new_level = 1;
+	}
+
+	new_dom_sizes.num_layers = new_level;
+
+	/* Allocate this now so we fail early */
+	new_hierarchy = kzalloc(sizeof(*new_hierarchy), GFP_KERNEL_ACCOUNT);
+	if (!new_hierarchy)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * Figure out how many indices and layer structs.  From this point
+	 * until we actually merge in the ruleset, ruleset must not change.
+	 */
+	mutex_lock(&ruleset->lock);
+	err = merge_domain(parent, &new_dom_sizes, ruleset, true);
+	if (err)
+		goto out_unlock;
+
+	/*
+	 * Ok, we know the required size now, allocate the domain and merge in
+	 * the indices and rules.
+	 */
+	new_dom = landlock_alloc_domain(&new_dom_sizes);
+	if (!new_dom) {
+		err = -ENOMEM;
+		goto out_unlock;
+	}
+	new_dom->hierarchy = new_hierarchy;
+	new_hierarchy = NULL;
+	refcount_set(&new_dom->hierarchy->usage, 1);
+
+	err = merge_domain(parent, new_dom, ruleset, false);
+	if (err) {
+		/* new_dom can contain invalid landlock_object references. */
+		kfree(new_dom);
+		new_dom = NULL;
+		goto out_unlock;
+	}
+
+	for (size_t i = 0; i < new_dom->num_fs_indices; i++)
+		landlock_get_object(dom_fs_indices(new_dom)[i].key.object);
+
+	err = inherit_domain(parent, new_dom, ruleset);
+	if (err)
+		goto out_unlock;
+
+	mutex_unlock(&ruleset->lock);
+
+	err = landlock_init_hierarchy_log(new_dom->hierarchy);
+	if (err)
+		return ERR_PTR(err);
+
+	return no_free_ptr(new_dom);
+
+out_unlock:
+	mutex_unlock(&ruleset->lock);
+	return ERR_PTR(err);
+}
+
 #ifdef CONFIG_AUDIT
 
 /**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 7af32810003c..1d9608439781 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -251,6 +251,10 @@ landlock_domain_find(const struct landlock_domain_index *const indices_arr,
 	for (layer = (found_rule).layers_start;    \
 	     layer < (found_rule).layers_end; layer++)
 
+struct landlock_domain *
+landlock_domain_merge_ruleset(const struct landlock_domain *parent,
+			      struct landlock_ruleset *ruleset);
+
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
 	LANDLOCK_LOG_RECORDED,
-- 
2.49.0




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