[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