[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