[RFC PATCH 08/10] landlock: Construct the inode hashtable in the new landlock_domain

Tingmao Wang m at maowtm.org
Wed May 21 19:32:04 UTC 2025


Since we can't get rid of the old landlock_merge_ruleset yet, we call our
new thing landlock_merge_ruleset2.

Signed-off-by: Tingmao Wang <m at maowtm.org>
---
 security/landlock/domain.c   |  87 +++++++++++++++++++++++++++++
 security/landlock/domain.h   |   4 ++
 security/landlock/hash.h     | 105 +++++++++++++++++++++++++++++++++++
 security/landlock/ruleset.h  |   2 +-
 security/landlock/syscalls.c |   9 +++
 5 files changed, 206 insertions(+), 1 deletion(-)

diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 321c52b275fc..fae21b260591 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -77,6 +77,93 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
 	}
 }
 
+/**
+ * @curr_table may be NULL.
+ */
+static int merge_domain_table(const enum landlock_key_type key_type,
+			      const struct landlock_hashtable *const curr_table,
+			      struct landlock_hashtable *const new_table,
+			      struct landlock_layer new_layer,
+			      const struct rb_root *ruleset_rb_root)
+{
+	int err;
+	struct landlock_rule *iter, *iter2;
+
+	if (curr_table) {
+		err = landlock_hash_clone(new_table, curr_table, key_type);
+		if (err) {
+			return err;
+		}
+	}
+
+	/* Merge in new rules */
+	rbtree_postorder_for_each_entry_safe(iter, iter2, ruleset_rb_root,
+					     node) {
+		WARN_ON_ONCE(iter->layers[0].level != 0);
+		WARN_ON_ONCE(iter->num_layers != 1);
+		new_layer.access = iter->layers[0].access;
+
+		err = landlock_hash_upsert(new_table, iter->key, key_type,
+					   new_layer);
+		if (err) {
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * @landlock_merge_ruleset2 - Merge a ruleset with a (possibly NULL)
+ * domain, and return a new merged domain.
+ */
+struct landlock_domain *
+landlock_merge_ruleset2(const struct landlock_domain *curr_domain,
+			const struct landlock_ruleset *next_ruleset)
+{
+	size_t num_inodes = 0;
+	int err;
+	struct landlock_domain *new_domain;
+	struct landlock_layer new_layer = {
+		.level = 1,
+	};
+	struct landlock_rule *iter, *iter2;
+
+	if (WARN_ON_ONCE(!next_ruleset)) {
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (curr_domain) {
+		new_layer.level = curr_domain->num_layers + 1;
+		num_inodes = landlock_hash_count(&curr_domain->inode_table);
+	}
+
+	/* Find new expected size of inode table */
+	rbtree_postorder_for_each_entry_safe(iter, iter2,
+					     &next_ruleset->root_inode, node) {
+		if (!curr_domain ||
+		    landlock_hash_find(&curr_domain->inode_table, iter->key) ==
+			    NULL) {
+			num_inodes += 1;
+		}
+	}
+
+	new_domain = landlock_alloc_domain(num_inodes, new_layer.level);
+	if (!new_domain)
+		return ERR_PTR(-ENOMEM);
+
+	err = merge_domain_table(LANDLOCK_KEY_INODE,
+				 curr_domain ? &curr_domain->inode_table : NULL,
+				 &new_domain->inode_table, new_layer,
+				 &next_ruleset->root_inode);
+	if (err) {
+		landlock_put_domain(new_domain);
+		return ERR_PTR(err);
+	}
+
+	return new_domain;
+}
+
 #ifdef CONFIG_AUDIT
 
 /**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 944420231040..e52b32d8dd2b 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -62,6 +62,10 @@ void landlock_put_domain(struct landlock_domain *const domain);
 
 void landlock_put_domain_deferred(struct landlock_domain *const domain);
 
+struct landlock_domain *
+landlock_merge_ruleset2(const struct landlock_domain *curr_domain,
+			const struct landlock_ruleset *new_ruleset);
+
 enum landlock_log_status {
 	LANDLOCK_LOG_PENDING = 0,
 	LANDLOCK_LOG_RECORDED,
diff --git a/security/landlock/hash.h b/security/landlock/hash.h
index 8393593cfe7b..8208944c309e 100644
--- a/security/landlock/hash.h
+++ b/security/landlock/hash.h
@@ -10,6 +10,7 @@
 
 #include <linux/slab.h>
 #include <linux/hash.h>
+#include <linux/rculist.h>
 
 #include "ruleset.h"
 
@@ -125,3 +126,107 @@ static inline size_t landlock_hash_count(const struct landlock_hashtable *ht)
 	}
 	return num_entries;
 }
+
+/**
+ * @landlock_hash_insert - Insert a rule in the hashtable, taking
+ * ownership of the passed in struct landlock_rule. This function assumes
+ * that the rule is already in the hash table.
+ */
+static inline void landlock_hash_insert(const struct landlock_hashtable *ht,
+					struct landlock_rule *const new_rule)
+{
+	struct hlist_head *head =
+		&ht->hlist[landlock_hash_key(new_rule->key, ht->hash_bits)];
+
+	hlist_add_head(&new_rule->hlist, head);
+}
+
+static inline int
+landlock_hash_clone(struct landlock_hashtable *const dst,
+		    const struct landlock_hashtable *const src,
+		    const enum landlock_key_type key_type)
+{
+	struct landlock_rule *curr_rule, *new_rule;
+	struct landlock_id id = {
+		.type = key_type,
+	};
+	size_t i;
+
+	landlock_hash_for_each(curr_rule, src, i)
+	{
+		id.key = curr_rule->key;
+		new_rule = landlock_create_rule(id, &curr_rule->layers,
+						curr_rule->num_layers, NULL);
+
+		if (IS_ERR(new_rule)) {
+			return PTR_ERR(new_rule);
+		}
+
+		/*
+		 * new_rule->hlist is invalid, but should still be safe to pass to
+		 * hlist_add_head().
+		 */
+		landlock_hash_insert(dst, new_rule);
+	}
+
+	return 0;
+}
+
+/**
+ * @landlock_hash_upsert - Either insert a new rule with the new layer in
+ * the hashtable, or update an existing one, adding the new layer.
+ *
+ * Hash table must have at least one slot.  This function doesn't take any
+ * locks - it's only valid to call this on a newly created (not yet
+ * committed to creds) domain.
+ *
+ * May error with -ENOMEM.
+ */
+static inline int landlock_hash_upsert(struct landlock_hashtable *const ht,
+				       union landlock_key key,
+				       const enum landlock_key_type key_type,
+				       struct landlock_layer new_layer)
+{
+	size_t index = landlock_hash_key(key, ht->hash_bits);
+	struct hlist_head *head = &ht->hlist[index];
+	struct landlock_rule *curr_rule, *new_rule;
+	const struct landlock_id id = {
+		.type = key_type,
+		.key = key,
+	};
+
+	hlist_for_each_entry(curr_rule, head, hlist) {
+		if (curr_rule->key.data != key.data)
+			continue;
+
+		new_rule = landlock_create_rule(id, &curr_rule->layers,
+						curr_rule->num_layers,
+						&new_layer);
+		if (IS_ERR(new_rule))
+			return PTR_ERR(new_rule);
+
+		/*
+		 * Replace curr_rule with new_rule in place within the hlist
+		 * We don't really care about RCU... but there's no "hlist_replace"
+		 * We should be safe to call hlist_replace_rcu() without first
+		 * initializing new_rule->hlist
+		 */
+		hlist_replace_rcu(&curr_rule->hlist, &new_rule->hlist);
+		free_rule(curr_rule, key_type);
+		return 0;
+	}
+
+	/* No existing rules found, insert new one. */
+	new_rule = landlock_create_rule(id, NULL, 0, &new_layer);
+	if (IS_ERR(new_rule))
+		return PTR_ERR(new_rule);
+
+	/*
+	 * new_rule->hlist is invalid, but should still be safe to pass to
+	 * hlist_add_head().
+	 */
+	hlist_add_head(&new_rule->hlist, head);
+	return 0;
+}
+
+#endif /* _SECURITY_LANDLOCK_HASH_H */
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 07823771b402..ac91d4a865b9 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -27,7 +27,7 @@ struct landlock_hierarchy;
  */
 struct landlock_layer {
 	/**
-	 * @level: Position of this layer in the layer stack.
+	 * @level: Position of this layer in the layer stack. Starts from 1.
 	 */
 	u16 level;
 	/**
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 38eb8287f73d..57ac3ee02f22 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -483,6 +483,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 		*ruleset __free(landlock_put_ruleset) = NULL;
 	struct cred *new_cred;
 	struct landlock_cred_security *new_llcred;
+	struct landlock_domain *new_domain2;
 	bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
 		prev_log_subdomains;
 
@@ -551,6 +552,12 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 		abort_creds(new_cred);
 		return PTR_ERR(new_dom);
 	}
+	new_domain2 = landlock_merge_ruleset2(new_llcred->domain2, ruleset);
+	if (IS_ERR(new_domain2)) {
+		landlock_put_ruleset(new_dom);
+		abort_creds(new_cred);
+		return PTR_ERR(new_domain2);
+	}
 
 #ifdef CONFIG_AUDIT
 	new_dom->hierarchy->log_same_exec = log_same_exec;
@@ -588,6 +595,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	/* Replaces the old (prepared) domain. */
 	landlock_put_ruleset(new_llcred->domain);
 	new_llcred->domain = new_dom;
+	landlock_put_domain(new_llcred->domain2);
+	new_llcred->domain2 = new_domain2;
 
 #ifdef CONFIG_AUDIT
 	new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
-- 
2.49.0




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