[RFC PATCH v2 07/12] landlock/domain: Define alloc and free

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


We will eventually need a deferred free just like we currently have for
the ruleset, so we define it here as well.  To minimize the size of the
domain struct before the rules array, we separately allocate the
work_struct (which is currently 72 bytes) and just keep a pointer in the
domain.

This patch triggers another (false positive?) checkpatch warning:

	ERROR: trailing statements should be on next line
	#177: FILE: security/landlock/domain.h:197:
	 DEFINE_FREE(landlock_put_domain, struct landlock_domain *,
	+	    if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T))

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

diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 89d36736a59c..a7474b803c47 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -33,6 +33,102 @@ static void build_check_domain(void)
 		     U32_MAX - 1);
 }
 
+/**
+ * landlock_alloc_domain - allocate a new domain given known sizes.  The
+ * caller must then at least fill in the indices and layers arrays before
+ * trying to free this domain.
+ *
+ * @sizes: A "fake" struct landlock_domain which just contains various
+ * num_* numbers.  This function will call dom_rules_len() to compute the
+ * total rules array length, and copy over the number fields.
+ * sizes.usage, sizes.hierarchy and sizes.work_free are ignored.
+ */
+struct landlock_domain *
+landlock_alloc_domain(const struct landlock_domain *sizes)
+{
+	u32 len_rules = dom_rules_len(sizes);
+	struct landlock_domain *new_dom = kzalloc(
+		struct_size(new_dom, rules, len_rules), GFP_KERNEL_ACCOUNT);
+
+	if (!new_dom)
+		return NULL;
+
+	memcpy(new_dom, sizes, sizeof(*sizes));
+	new_dom->hierarchy = NULL;
+	new_dom->work_free =
+		kzalloc(sizeof(*new_dom->work_free), GFP_KERNEL_ACCOUNT);
+	if (!new_dom->work_free) {
+		kfree(new_dom);
+		return NULL;
+	}
+	new_dom->work_free->domain = new_dom;
+	refcount_set(&new_dom->usage, 1);
+	new_dom->len_rules = len_rules;
+
+	return new_dom;
+}
+
+static void free_domain(struct landlock_domain *const domain)
+{
+	struct landlock_domain_index *fs_indices, ind;
+	u32 i, num_fs_indices;
+
+	might_sleep();
+	if (WARN_ON_ONCE(!domain))
+		return;
+	fs_indices = dom_fs_indices(domain);
+	num_fs_indices = domain->num_fs_indices;
+	if (WARN_ON_ONCE((uintptr_t *)(fs_indices + num_fs_indices) -
+				 domain->rules >
+			 domain->len_rules))
+		return;
+
+	for (i = 0; i < num_fs_indices; i++) {
+		ind = fs_indices[i];
+		landlock_put_object(ind.key.object);
+	}
+
+	landlock_put_hierarchy(domain->hierarchy);
+	domain->hierarchy = NULL;
+	kfree(domain->work_free);
+	domain->work_free = NULL;
+	kfree(domain);
+}
+
+void landlock_put_domain(struct landlock_domain *const domain)
+{
+	might_sleep();
+
+	if (domain && refcount_dec_and_test(&domain->usage))
+		free_domain(domain);
+}
+
+static void free_domain_work(struct work_struct *const work)
+{
+	struct landlock_domain_work_free *const fw =
+		container_of(work, struct landlock_domain_work_free, work);
+	struct landlock_domain *domain = fw->domain;
+
+	free_domain(domain);
+	/* the work_free struct will be freed by free_domain */
+}
+
+/*
+ * Schedule work to free a landlock_domain, useful in a non-sleepable
+ * context.
+ */
+void landlock_put_domain_deferred(struct landlock_domain *const domain)
+{
+	if (domain && refcount_dec_and_test(&domain->usage)) {
+		INIT_WORK(&domain->work_free->work, free_domain_work);
+
+		if (WARN_ON_ONCE(domain->work_free->domain != domain))
+			return;
+
+		schedule_work(&domain->work_free->work);
+	}
+}
+
 /**
  * dom_calculate_merged_sizes - Calculate the eventual size of the part of
  * a new domain for a given rule size (i.e. either fs or net).  Correct
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 8acd88a1d77a..7af32810003c 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -44,7 +44,30 @@ struct landlock_domain_index {
 	u32 layer_index;
 };
 
+struct landlock_domain_work_free {
+	struct work_struct work;
+	struct landlock_domain *domain;
+};
+
 struct landlock_domain {
+	/**
+	 * @hierarchy: Enables hierarchy identification even when a parent
+	 * domain vanishes.  This is needed for the ptrace protection.
+	 */
+	struct landlock_hierarchy *hierarchy;
+	/**
+	 * @work_free: Enables to free a domain within a lockless section.
+	 * This is only used by landlock_put_domain_deferred() when @usage
+	 * reaches zero. This is a pointer to an allocated struct in order to
+	 * minimize the size of this struct. To prevent needing to allocate
+	 * when freeing, this is pre-allocated on domain creation.
+	 */
+	struct landlock_domain_work_free *work_free;
+	/**
+	 * @usage: Number of processes (i.e. domains) or file descriptors
+	 * referencing this ruleset.
+	 */
+	refcount_t usage;
 	/**
 	 * @num_layers: Number of layers in this domain.  This enables to
 	 * check that all the layers allow an access request.
@@ -158,6 +181,21 @@ DEFINE_COALESCED_HASH_TABLE(struct landlock_domain_index, dom_hash, key,
 			    hash_long(elem->key.data, 32) % table_size,
 			    dom_index_is_empty(elem))
 
+struct landlock_domain *
+landlock_alloc_domain(const struct landlock_domain *sizes);
+
+static inline void landlock_get_domain(struct landlock_domain *const domain)
+{
+	if (domain)
+		refcount_inc(&domain->usage);
+}
+
+void landlock_put_domain(struct landlock_domain *const domain);
+void landlock_put_domain_deferred(struct landlock_domain *const domain);
+
+DEFINE_FREE(landlock_put_domain, struct landlock_domain *,
+	    if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T))
+
 struct landlock_found_rule {
 	const struct landlock_layer *layers_start;
 	const struct landlock_layer *layers_end;
-- 
2.49.0




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