* FAILED: patch "[PATCH] landlock: Work around gcc-16 -Wuninitialized warning" failed to apply to 7.2-stable tree
@ 2026-09-29 6:48 gregkh
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
0 siblings, 1 reply; 7+ messages in thread
From: gregkh @ 2026-09-29 6:48 UTC (permalink / raw)
To: arnd, mic; +Cc: stable
The patch below does not apply to the 7.2-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.2.y
git checkout FETCH_HEAD
git cherry-pick -x c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026092944-secrecy-crouch-5bf3@gregkh' --subject-prefix 'PATCH 7.2.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d Mon Sep 17 00:00:00 2001
From: Arnd Bergmann <arnd@arndb.de>
Date: Tue, 15 Sep 2026 22:10:30 +0200
Subject: [PATCH] landlock: Work around gcc-16 -Wuninitialized warning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
gcc has a bug with -ftrivial-auto-var-init=pattern that produces a
warning for correct code that uses sparse bitfields:
security/landlock/fs.c: In function 'is_access_to_paths_allowed.isra':
security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized]
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c:767:28: note: '_layer_masks_child1' declared here
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c: In function 'hook_unix_find':
security/landlock/fs.c:1649:28: error: 'layer_masks' is used uninitialized [-Werror=uninitialized]
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
security/landlock/fs.c:1649:28: note: 'layer_masks' declared here
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
To work around this, change the definition of struct layer_mask to
use an explictit padding field.
Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743
Link: https://lore.kernel.org/all/20260619082133.3504146-1-arnd@kernel.org/
Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Link: https://patch.msgid.link/20260915201036.3527935-1-arnd@kernel.org
[mic: Use BITS_PER_TYPE(), fix kdoc warnings, fix commit message
according to v2 changes]
Cc: stable@vger.kernel.org
Signed-off-by: Mickaël Salaün <mic@digikod.net>
diff --git a/security/landlock/access.h b/security/landlock/access.h
index bbbb41f41147..f843835851d0 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -61,6 +61,10 @@ union access_masks_all {
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
+#define _LANDLOCK_LAYER_MASK_PADDING \
+ (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \
+ IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG))
+
/**
* struct layer_mask - The access rights and rule flags for a layer.
*
@@ -81,6 +85,10 @@ struct layer_mask {
*/
access_mask_t quiet : 1;
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ /**
+ * @__pad: Padding for the compiler's bitfield initialization.
+ */
+ access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING;
} __packed __aligned(sizeof(access_mask_t));
/*
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split
2026-09-29 6:48 FAILED: patch "[PATCH] landlock: Work around gcc-16 -Wuninitialized warning" failed to apply to 7.2-stable tree gregkh
@ 2026-09-29 13:46 ` Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 2/5] landlock: Move domain query functions to domain.c Sasha Levin
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Sasha Levin @ 2026-09-29 13:46 UTC (permalink / raw)
To: stable
Cc: Mickaël Salaün, Günther Noack, Tingmao Wang,
Sasha Levin
From: Mickaël Salaün <mic@digikod.net>
[ Upstream commit e76ef456bb673c7fc5def433502a338631df0ebf ]
Rulesets and domains serve fundamentally different purposes: a ruleset
is mutable and user-facing, created by landlock_create_ruleset(), while
a domain is immutable after construction and enforced on tasks via
landlock_restrict_self(). Today both are represented by struct
landlock_ruleset, which conflates mutable and immutable state in a
single type: the lock field is unused by domains, the hierarchy field is
unused by rulesets, and lifecycle functions must handle both cases.
Prepare for a clean type split by introducing two new structures:
- struct landlock_rules: the red-black tree roots and rule count, shared
by both rulesets and domains. Decoupling rule storage from the domain
API lets the backing data structure change independently (e.g. to a
hash table, cf. [1]).
- struct landlock_domain: the immutable domain enforced on tasks, with
no lock field because its rules and access masks are fixed once
construction completes. The name reflects the role, not the internal
data structure.
Add the domain lifecycle helpers (landlock_get_domain(),
landlock_put_domain(), landlock_put_domain_deferred()) and move domain.o
from landlock-$(CONFIG_AUDIT) to landlock-y, because these are needed
unconditionally, not just for audit logging.
No behavioral change. The new types and lifecycle functions are not yet
used by any caller.
Cc: Günther Noack <gnoack@google.com>
Link: https://patch.msgid.link/20250523165741.693976-1-mic@digikod.net [1]
Link: https://patch.msgid.link/20260811094338.288094-2-mic@digikod.net
Reviewed-by: Tingmao Wang <m@maowtm.org>
[mic: Update copyright]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Stable-dep-of: c4e941bb7654 ("landlock: Work around gcc-16 -Wuninitialized warning")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/landlock/Makefile | 6 +--
security/landlock/domain.c | 36 +++++++++++++++++
security/landlock/domain.h | 70 ++++++++++++++++++++++++++++++++
security/landlock/ruleset.c | 71 ++++++++++++++++-----------------
security/landlock/ruleset.h | 79 +++++++++++++++++++++++++++----------
5 files changed, 202 insertions(+), 60 deletions(-)
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index ffa7646d99f3e..23e13644916f7 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -8,11 +8,11 @@ landlock-y := \
cred.o \
task.o \
fs.o \
- tsync.o
+ tsync.o \
+ domain.o
landlock-$(CONFIG_INET) += net.o
landlock-$(CONFIG_AUDIT) += \
id.o \
- audit.o \
- domain.o
+ audit.o
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 9a8355fccd26e..a8680ca2ac368 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -5,6 +5,7 @@
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
*/
#include <kunit/test.h>
@@ -15,14 +16,49 @@
#include <linux/mm.h>
#include <linux/path.h>
#include <linux/pid.h>
+#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/signal.h>
+#include <linux/slab.h>
#include <linux/uidgid.h>
+#include <linux/workqueue.h>
#include "access.h"
#include "common.h"
#include "domain.h"
#include "id.h"
+#include "ruleset.h"
+
+static void free_domain(struct landlock_domain *const domain)
+{
+ might_sleep();
+ landlock_free_rules(&domain->rules);
+ landlock_put_hierarchy(domain->hierarchy);
+ 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 *domain;
+
+ domain = container_of(work, struct landlock_domain, work_free);
+ free_domain(domain);
+}
+
+void landlock_put_domain_deferred(struct landlock_domain *const domain)
+{
+ if (domain && refcount_dec_and_test(&domain->usage)) {
+ INIT_WORK(&domain->work_free, free_domain_work);
+ schedule_work(&domain->work_free);
+ }
+}
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 2a1660e3dea7e..8d6de80a4b294 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -5,11 +5,13 @@
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
*/
#ifndef _SECURITY_LANDLOCK_DOMAIN_H
#define _SECURITY_LANDLOCK_DOMAIN_H
+#include <linux/cleanup.h>
#include <linux/limits.h>
#include <linux/mm.h>
#include <linux/path.h>
@@ -17,9 +19,11 @@
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/workqueue.h>
#include "access.h"
#include "audit.h"
+#include "ruleset.h"
enum landlock_log_status {
LANDLOCK_LOG_PENDING = 0,
@@ -176,4 +180,70 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
}
}
+/**
+ * struct landlock_domain - Immutable Landlock domain
+ *
+ * A domain is created from a ruleset by landlock_merge_ruleset() and enforced
+ * on a task. Once created, its rules and access masks are immutable. Unlike
+ * &struct landlock_ruleset, a domain has no lock field.
+ */
+struct landlock_domain {
+ /**
+ * @rules: Red-black tree storage for rules.
+ */
+ struct landlock_rules rules;
+ /**
+ * @hierarchy: Enables hierarchy identification even when a parent
+ * domain vanishes. This is needed for the ptrace and scope
+ * restrictions.
+ */
+ struct landlock_hierarchy *hierarchy;
+ union {
+ /**
+ * @work_free: Enables to free a domain within a lockless
+ * section. This is only used by landlock_put_domain_deferred()
+ * when @usage reaches zero. The fields @usage, @num_layers and
+ * @access_masks are then unused.
+ */
+ struct work_struct work_free;
+ struct {
+ /**
+ * @usage: Number of credentials referencing this
+ * domain.
+ */
+ refcount_t usage;
+ /**
+ * @num_layers: Number of layers that are used in this
+ * domain. This enables to check that all the layers
+ * allow an access request.
+ */
+ u32 num_layers;
+ /**
+ * @access_masks: Contains the subset of filesystem and
+ * network actions that are restricted by a domain. A
+ * domain saves all layers of merged rulesets in a stack
+ * (FAM), starting from the first layer to the last one.
+ * These layers are used when merging rulesets, for user
+ * space backward compatibility (i.e. future-proof), and
+ * to properly handle merged rulesets without
+ * overlapping access rights. These layers are set once
+ * and never changed for the lifetime of the domain.
+ */
+ struct access_masks access_masks[];
+ };
+ };
+};
+
+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))
+
+static inline void landlock_get_domain(struct landlock_domain *const domain)
+{
+ if (domain)
+ refcount_inc(&domain->usage);
+}
+
#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4dd09ea22c844..9e560aed64b6f 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -39,16 +39,16 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
return ERR_PTR(-ENOMEM);
refcount_set(&new_ruleset->usage, 1);
mutex_init(&new_ruleset->lock);
- new_ruleset->root_inode = RB_ROOT;
+ new_ruleset->rules.root_inode = RB_ROOT;
#if IS_ENABLED(CONFIG_INET)
- new_ruleset->root_net_port = RB_ROOT;
+ new_ruleset->rules.root_net_port = RB_ROOT;
#endif /* IS_ENABLED(CONFIG_INET) */
new_ruleset->num_layers = num_layers;
/*
* hierarchy = NULL
- * num_rules = 0
+ * rules.num_rules = 0
* access_masks[] = 0
*/
return new_ruleset;
@@ -148,19 +148,7 @@ create_rule(const struct landlock_id id,
static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
const enum landlock_key_type key_type)
{
- switch (key_type) {
- case LANDLOCK_KEY_INODE:
- return &ruleset->root_inode;
-
-#if IS_ENABLED(CONFIG_INET)
- case LANDLOCK_KEY_NET_PORT:
- return &ruleset->root_net_port;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- default:
- WARN_ON_ONCE(1);
- return ERR_PTR(-EINVAL);
- }
+ return landlock_get_rule_root(&ruleset->rules, key_type);
}
static void free_rule(struct landlock_rule *const rule,
@@ -176,19 +164,24 @@ static void free_rule(struct landlock_rule *const rule,
static void build_check_ruleset(void)
{
- const struct landlock_ruleset ruleset = {
+ const struct landlock_rules rules = {
.num_rules = ~0,
+ };
+ const struct landlock_ruleset ruleset = {
.num_layers = ~0,
};
- BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
+ BUILD_BUG_ON(rules.num_rules < LANDLOCK_MAX_NUM_RULES);
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
}
/**
- * insert_rule - Create and insert a rule in a ruleset
+ * insert_rule - Create and insert a rule into the rule storage
*
- * @ruleset: The ruleset to be updated.
+ * @rules: The rule storage to be updated. The caller is responsible for
+ * any required locking. For rulesets, this means holding
+ * &landlock_ruleset.lock. For domains under construction, no lock is
+ * needed because the domain is not yet visible to other tasks.
* @id: The ID to build the new rule with. The underlying kernel object, if
* any, must be held by the caller.
* @layers: One or multiple layers to be copied into the new rule.
@@ -196,16 +189,16 @@ static void build_check_ruleset(void)
*
* When user space requests to add a new rule to a ruleset, @layers only
* contains one entry and this entry is not assigned to any level. In this
- * case, the new rule will extend @ruleset, similarly to a boolean OR between
+ * case, the new rule will extend @rules, similarly to a boolean OR between
* access rights.
*
* When merging a ruleset in a domain, or copying a domain, @layers will be
- * added to @ruleset as new constraints, similarly to a boolean AND between
- * access rights.
+ * added to @rules as new constraints, similarly to a boolean AND between access
+ * rights.
*
* Return: 0 on success, -errno on failure.
*/
-static int insert_rule(struct landlock_ruleset *const ruleset,
+static int insert_rule(struct landlock_rules *const rules,
const struct landlock_id id,
const struct landlock_layer (*layers)[],
const size_t num_layers)
@@ -216,14 +209,13 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
struct rb_root *root;
might_sleep();
- lockdep_assert_held(&ruleset->lock);
if (WARN_ON_ONCE(!layers))
return -ENOENT;
if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object))
return -ENOENT;
- root = get_root(ruleset, id.type);
+ root = landlock_get_rule_root(rules, id.type);
if (IS_ERR(root))
return PTR_ERR(root);
@@ -249,7 +241,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
if ((*layers)[0].level == 0) {
/*
* Extends access rights when the request comes from
- * landlock_add_rule(2), i.e. @ruleset is not a domain.
+ * landlock_add_rule(2), i.e. contained by a ruleset.
*/
if (WARN_ON_ONCE(this->num_layers != 1))
return -EINVAL;
@@ -278,14 +270,14 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
/* There is no match for @id. */
build_check_ruleset();
- if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
+ if (rules->num_rules >= LANDLOCK_MAX_NUM_RULES)
return -E2BIG;
new_rule = create_rule(id, layers, num_layers, NULL);
if (IS_ERR(new_rule))
return PTR_ERR(new_rule);
rb_link_node(&new_rule->node, parent_node, walker_node);
rb_insert_color(&new_rule->node, root);
- ruleset->num_rules++;
+ rules->num_rules++;
return 0;
}
@@ -319,7 +311,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
} };
build_check_layer();
- return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
+ lockdep_assert_held(&ruleset->lock);
+ return insert_rule(&ruleset->rules, id, &layers, ARRAY_SIZE(layers));
}
static int merge_tree(struct landlock_ruleset *const dst,
@@ -358,7 +351,7 @@ static int merge_tree(struct landlock_ruleset *const dst,
layers[0].access = walker_rule->layers[0].access;
layers[0].flags = walker_rule->layers[0].flags;
- err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
+ err = insert_rule(&dst->rules, id, &layers, ARRAY_SIZE(layers));
if (err)
return err;
}
@@ -432,7 +425,7 @@ static int inherit_tree(struct landlock_ruleset *const parent,
.type = key_type,
};
- err = insert_rule(child, id, &walker_rule->layers,
+ err = insert_rule(&child->rules, id, &walker_rule->layers,
walker_rule->num_layers);
if (err)
return err;
@@ -486,21 +479,26 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
return err;
}
-static void free_ruleset(struct landlock_ruleset *const ruleset)
+void landlock_free_rules(struct landlock_rules *const rules)
{
struct landlock_rule *freeme, *next;
might_sleep();
- rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+ rbtree_postorder_for_each_entry_safe(freeme, next, &rules->root_inode,
node)
free_rule(freeme, LANDLOCK_KEY_INODE);
#if IS_ENABLED(CONFIG_INET)
rbtree_postorder_for_each_entry_safe(freeme, next,
- &ruleset->root_net_port, node)
+ &rules->root_net_port, node)
free_rule(freeme, LANDLOCK_KEY_NET_PORT);
#endif /* IS_ENABLED(CONFIG_INET) */
+}
+static void free_ruleset(struct landlock_ruleset *const ruleset)
+{
+ might_sleep();
+ landlock_free_rules(&ruleset->rules);
landlock_put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
@@ -604,7 +602,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct rb_root *root;
const struct rb_node *node;
- root = get_root((struct landlock_ruleset *)ruleset, id.type);
+ root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
+ id.type);
if (IS_ERR(root))
return NULL;
node = root->rb_node;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0437adf174287..c8c83efff4c58 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -68,13 +68,12 @@ union landlock_key {
*/
enum landlock_key_type {
/**
- * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
- * keys.
+ * @LANDLOCK_KEY_INODE: Type of &landlock_rules.root_inode's node keys.
*/
LANDLOCK_KEY_INODE = 1,
/**
- * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
- * node keys.
+ * @LANDLOCK_KEY_NET_PORT: Type of &landlock_rules.root_net_port's node
+ * keys.
*/
LANDLOCK_KEY_NET_PORT,
};
@@ -122,30 +121,44 @@ struct landlock_rule {
};
/**
- * struct landlock_ruleset - Landlock ruleset
+ * struct landlock_rules - Red-black tree storage for Landlock rules
*
- * This data structure must contain unique entries, be updatable, and quick to
- * match an object.
+ * This structure holds the rule trees shared by both rulesets and domains.
*/
-struct landlock_ruleset {
+struct landlock_rules {
/**
* @root_inode: Root of a red-black tree containing &struct
- * landlock_rule nodes with inode object. Once a ruleset is tied to a
- * process (i.e. as a domain), this tree is immutable until @usage
- * reaches zero.
+ * landlock_rule nodes with inode object. Immutable for domains.
*/
struct rb_root root_inode;
#if IS_ENABLED(CONFIG_INET)
/**
* @root_net_port: Root of a red-black tree containing &struct
- * landlock_rule nodes with network port. Once a ruleset is tied to a
- * process (i.e. as a domain), this tree is immutable until @usage
- * reaches zero.
+ * landlock_rule nodes with network port. Immutable for domains.
*/
struct rb_root root_net_port;
#endif /* IS_ENABLED(CONFIG_INET) */
+ /**
+ * @num_rules: Number of non-overlapping (i.e. not for the same object)
+ * rules in this tree storage.
+ */
+ u32 num_rules;
+};
+
+/**
+ * struct landlock_ruleset - Landlock ruleset
+ *
+ * This data structure must contain unique entries, be updatable, and quick to
+ * match an object.
+ */
+struct landlock_ruleset {
+ /**
+ * @rules: Red-black tree storage for rules.
+ */
+ struct landlock_rules rules;
+
/**
* @hierarchy: Enables hierarchy identification even when a parent
* domain vanishes. This is needed for the ptrace protection.
@@ -156,8 +169,8 @@ struct landlock_ruleset {
* @work_free: Enables to free a ruleset within a lockless
* section. This is only used by
* landlock_put_ruleset_deferred() when @usage reaches zero.
- * The fields @lock, @usage, @num_rules, @num_layers,
- * @quiet_masks and @access_masks are then unused.
+ * The fields @lock, @usage, @num_layers, @quiet_masks and
+ * @access_masks are then unused.
*/
struct work_struct work_free;
struct {
@@ -171,11 +184,6 @@ struct landlock_ruleset {
* descriptors referencing this ruleset.
*/
refcount_t usage;
- /**
- * @num_rules: Number of non-overlapping (i.e. not for
- * the same object) rules in this ruleset.
- */
- u32 num_rules;
/**
* @num_layers: Number of layers that are used in this
* ruleset. This enables to check that all the layers
@@ -221,6 +229,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
const access_mask_t access, const u32 flags);
+void landlock_free_rules(struct landlock_rules *const rules);
+
struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
struct landlock_ruleset *const ruleset);
@@ -229,6 +239,33 @@ const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
const struct landlock_id id);
+/**
+ * landlock_get_rule_root - Get the root of a rule tree by key type
+ *
+ * @rules: The rules storage to look up.
+ * @key_type: The type of key to select the tree for.
+ *
+ * Return: A pointer to the rb_root, or ERR_PTR(-EINVAL) on unknown type.
+ */
+static inline struct rb_root *
+landlock_get_rule_root(struct landlock_rules *const rules,
+ const enum landlock_key_type key_type)
+{
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return &rules->root_inode;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return &rules->root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return ERR_PTR(-EINVAL);
+ }
+}
+
static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
{
if (ruleset)
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y 2/5] landlock: Move domain query functions to domain.c
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
@ 2026-09-29 13:46 ` Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 3/5] landlock: Split struct landlock_domain from struct landlock_ruleset Sasha Levin
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-09-29 13:46 UTC (permalink / raw)
To: stable
Cc: Mickaël Salaün, Günther Noack, Tingmao Wang,
Sasha Levin
From: Mickaël Salaün <mic@digikod.net>
[ Upstream commit 2bbba0905a795b46c20159cecf44033c96b87d22 ]
Grouping domain-specific code in one compilation unit reduces coupling
between domain and ruleset implementations.
Move the access-check functions that only operate on a domain (rule
lookup, layer unmasking, layer-mask init, access-mask union) from
ruleset.[ch] to domain.[ch]. They evaluate whether a domain grants a
requested access during the pathwalk and network checks and do not
modify the domain.
The merge and inherit chain stays in ruleset.c for now because it calls
the static create_ruleset() allocator; a following commit moves it once
the domain type switch eliminates that dependency.
No behavioral change. The functions move with unchanged signatures and
bodies.
Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Link: https://patch.msgid.link/20260811094338.288094-3-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Stable-dep-of: c4e941bb7654 ("landlock: Work around gcc-16 -Wuninitialized warning")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/landlock/domain.c | 162 ++++++++++++++++++++++++++++++++++++
security/landlock/domain.h | 38 +++++++++
security/landlock/net.c | 1 +
security/landlock/ruleset.c | 150 ---------------------------------
security/landlock/ruleset.h | 38 ---------
5 files changed, 201 insertions(+), 188 deletions(-)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index a8680ca2ac368..3068576d14eae 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -11,11 +11,15 @@
#include <kunit/test.h>
#include <linux/bitops.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/cred.h>
+#include <linux/err.h>
#include <linux/file.h>
#include <linux/mm.h>
+#include <linux/overflow.h>
#include <linux/path.h>
#include <linux/pid.h>
+#include <linux/rbtree.h>
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/signal.h>
@@ -27,6 +31,7 @@
#include "common.h"
#include "domain.h"
#include "id.h"
+#include "limits.h"
#include "ruleset.h"
static void free_domain(struct landlock_domain *const domain)
@@ -60,6 +65,163 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
}
}
+/* The returned access has the same lifetime as the domain. */
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+ const struct landlock_id id)
+{
+ const struct rb_root *root;
+ const struct rb_node *node;
+
+ root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
+ id.type);
+ if (IS_ERR(root))
+ return NULL;
+ node = root->rb_node;
+
+ while (node) {
+ struct landlock_rule *this =
+ rb_entry(node, struct landlock_rule, node);
+
+ if (this->key.data == id.key.data)
+ return this;
+ if (this->key.data < id.key.data)
+ node = node->rb_right;
+ else
+ node = node->rb_left;
+ }
+ return NULL;
+}
+
+/**
+ * landlock_unmask_layers - Remove the access rights in @masks which are
+ * granted in @rule
+ *
+ * Updates the set of (per-layer) unfulfilled access rights @masks so that all
+ * the access rights granted in @rule are removed from it (because they are now
+ * fulfilled).
+ *
+ * @rule: A rule that grants a set of access rights for each layer.
+ * @masks: A matrix of unfulfilled access rights for each layer.
+ *
+ * Return: True if the request is allowed (i.e. the access rights granted all
+ * remaining unfulfilled access rights and masks has no leftover set bits).
+ */
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ struct layer_masks *masks)
+{
+ if (!masks)
+ return true;
+ if (!rule)
+ return false;
+
+ /*
+ * An access is granted if, for each policy layer, at least one rule
+ * encountered on the pathwalk grants the requested access, regardless
+ * of its position in the layer stack. We must then check the remaining
+ * layers for each inode, from the first added layer to the last one.
+ * When there are multiple requested accesses, for each policy layer,
+ * the full set of requested accesses may not be granted by only one
+ * rule, but by the union (binary OR) of multiple rules. For example,
+ * /a/b <execute> + /a <read> grants /a/b <execute + read>.
+ *
+ * This function is called once per matching rule during the pathwalk,
+ * progressively clearing bits in @masks. The overall access decision
+ * is per-layer: access is granted iff masks->layers[l].access == 0 for
+ * all layers l. When two independent mechanisms can each grant access
+ * within a layer (e.g. a path rule OR a scope exception), the
+ * composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not
+ * (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when
+ * different layers grant via different mechanisms.
+ */
+ for (size_t i = 0; i < rule->num_layers; i++) {
+ const struct landlock_layer *const layer = &rule->layers[i];
+
+ /* Clear the bits where the layer in the rule grants access. */
+ masks->layers[layer->level - 1].access &= ~layer->access;
+
+#ifdef CONFIG_AUDIT
+ /* Collect rule flags for each layer. */
+ if (layer->flags.quiet)
+ masks->layers[layer->level - 1].quiet = true;
+#endif /* CONFIG_AUDIT */
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+ if (masks->layers[i].access)
+ return false;
+ }
+ return true;
+}
+
+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level);
+
+/**
+ * landlock_init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @masks such that for each access right in @access_request, the bits
+ * for all the layers are set where this access right is handled. Rule flags
+ * are also zeroed.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @masks: Layer access masks to populate.
+ * @key_type: The key type to switch between access masks of different types.
+ *
+ * Return: An access mask where each access right bit is set which is handled in
+ * any of the active layers in @domain.
+ */
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ struct layer_masks *const masks,
+ const enum landlock_key_type key_type)
+{
+ access_mask_t handled_accesses = 0;
+ get_access_mask_t *get_access_mask;
+
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ get_access_mask = landlock_get_fs_access_mask;
+ break;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ get_access_mask = landlock_get_net_access_mask;
+ break;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /* An empty access request can happen because of O_WRONLY | O_RDWR. */
+ if (!access_request)
+ return 0;
+
+ for (size_t i = 0; i < domain->num_layers; i++) {
+ const access_mask_t handled = get_access_mask(domain, i);
+
+ masks->layers[i].access = access_request & handled;
+ handled_accesses |= masks->layers[i].access;
+#ifdef CONFIG_AUDIT
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
+ }
+ for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
+ i++) {
+ masks->layers[i].access = 0;
+#ifdef CONFIG_AUDIT
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
+ }
+
+ return handled_accesses;
+}
+
#ifdef CONFIG_AUDIT
/**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 8d6de80a4b294..315f72b0b34df 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -234,12 +234,50 @@ struct landlock_domain {
};
};
+/**
+ * landlock_union_access_masks - Return all access rights handled in the
+ * domain
+ *
+ * @domain: Landlock ruleset (used as a domain)
+ *
+ * Return: An access_masks result of the OR of all the domain's access masks.
+ */
+static inline struct access_masks
+landlock_union_access_masks(const struct landlock_ruleset *const domain)
+{
+ union access_masks_all matches = {};
+ size_t layer_level;
+
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+ union access_masks_all layer = {
+ .masks = domain->access_masks[layer_level],
+ };
+
+ matches.all |= layer.all;
+ }
+
+ return matches.masks;
+}
+
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))
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+ const struct landlock_id id);
+
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ struct layer_masks *masks);
+
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ struct layer_masks *masks,
+ const enum landlock_key_type key_type);
+
static inline void landlock_get_domain(struct landlock_domain *const domain)
{
if (domain)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 46c17116fcf42..feecc8c77ab4f 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -15,6 +15,7 @@
#include "audit.h"
#include "common.h"
#include "cred.h"
+#include "domain.h"
#include "limits.h"
#include "net.h"
#include "ruleset.h"
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 9e560aed64b6f..96e65804d2ff4 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -591,153 +591,3 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
return no_free_ptr(new_dom);
}
-
-/*
- * The returned access has the same lifetime as @ruleset.
- */
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_id id)
-{
- const struct rb_root *root;
- const struct rb_node *node;
-
- root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
- id.type);
- if (IS_ERR(root))
- return NULL;
- node = root->rb_node;
-
- while (node) {
- struct landlock_rule *this =
- rb_entry(node, struct landlock_rule, node);
-
- if (this->key.data == id.key.data)
- return this;
- if (this->key.data < id.key.data)
- node = node->rb_right;
- else
- node = node->rb_left;
- }
- return NULL;
-}
-
-/**
- * landlock_unmask_layers - Remove the access rights in @masks
- * which are granted in @rule
- *
- * Updates the set of (per-layer) unfulfilled access rights @masks
- * so that all the access rights granted in @rule are removed from it
- * (because they are now fulfilled).
- *
- * @rule: A rule that grants a set of access rights for each layer
- * @masks: A matrix of unfulfilled access rights for each layer
- *
- * Return: True if the request is allowed (i.e. the access rights granted all
- * remaining unfulfilled access rights and masks has no leftover set bits).
- */
-bool landlock_unmask_layers(const struct landlock_rule *const rule,
- struct layer_masks *masks)
-{
- if (!masks)
- return true;
- if (!rule)
- return false;
-
- /*
- * An access is granted if, for each policy layer, at least one rule
- * encountered on the pathwalk grants the requested access,
- * regardless of its position in the layer stack. We must then check
- * the remaining layers for each inode, from the first added layer to
- * the last one. When there is multiple requested accesses, for each
- * policy layer, the full set of requested accesses may not be granted
- * by only one rule, but by the union (binary OR) of multiple rules.
- * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
- */
- for (size_t i = 0; i < rule->num_layers; i++) {
- const struct landlock_layer *const layer = &rule->layers[i];
-
- /* Clear the bits where the layer in the rule grants access. */
- masks->layers[layer->level - 1].access &= ~layer->access;
-
-#ifdef CONFIG_AUDIT
- /* Collect rule flags for each layer. */
- if (layer->flags.quiet)
- masks->layers[layer->level - 1].quiet = true;
-#endif /* CONFIG_AUDIT */
- }
-
- for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
- if (masks->layers[i].access)
- return false;
- }
- return true;
-}
-
-typedef access_mask_t
-get_access_mask_t(const struct landlock_ruleset *const ruleset,
- const u16 layer_level);
-
-/**
- * landlock_init_layer_masks - Initialize layer masks from an access request
- *
- * Populates @masks such that for each access right in @access_request, the bits
- * for all the layers are set where this access right is handled. Rule flags
- * are also zeroed.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @masks: Layer access masks to populate.
- * @key_type: The key type to switch between access masks of different types.
- *
- * Return: An access mask where each access right bit is set which is handled
- * in any of the active layers in @domain.
- */
-access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
- const access_mask_t access_request,
- struct layer_masks *const masks,
- const enum landlock_key_type key_type)
-{
- access_mask_t handled_accesses = 0;
- get_access_mask_t *get_access_mask;
-
- switch (key_type) {
- case LANDLOCK_KEY_INODE:
- get_access_mask = landlock_get_fs_access_mask;
- break;
-
-#if IS_ENABLED(CONFIG_INET)
- case LANDLOCK_KEY_NET_PORT:
- get_access_mask = landlock_get_net_access_mask;
- break;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- default:
- WARN_ON_ONCE(1);
- return 0;
- }
-
- /* An empty access request can happen because of O_WRONLY | O_RDWR. */
- if (!access_request)
- return 0;
-
- for (size_t i = 0; i < domain->num_layers; i++) {
- const access_mask_t handled = get_access_mask(domain, i);
-
- masks->layers[i].access = access_request & handled;
- handled_accesses |= masks->layers[i].access;
-#ifdef CONFIG_AUDIT
- masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
- }
- for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
- i++) {
- masks->layers[i].access = 0;
-#ifdef CONFIG_AUDIT
- masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
- }
-
- return handled_accesses;
-}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index c8c83efff4c58..f9486c06b7ec3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -235,10 +235,6 @@ struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
struct landlock_ruleset *const ruleset);
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_id id);
-
/**
* landlock_get_rule_root - Get the root of a rule tree by key type
*
@@ -272,31 +268,6 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
-/**
- * landlock_union_access_masks - Return all access rights handled in the
- * domain
- *
- * @domain: Landlock ruleset (used as a domain)
- *
- * Return: An access_masks result of the OR of all the domain's access masks.
- */
-static inline struct access_masks
-landlock_union_access_masks(const struct landlock_ruleset *const domain)
-{
- union access_masks_all matches = {};
- size_t layer_level;
-
- for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
- union access_masks_all layer = {
- .masks = domain->access_masks[layer_level],
- };
-
- matches.all |= layer.all;
- }
-
- return matches.masks;
-}
-
static inline void
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
const access_mask_t fs_access_mask,
@@ -355,13 +326,4 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
return ruleset->access_masks[layer_level].scope;
}
-bool landlock_unmask_layers(const struct landlock_rule *const rule,
- struct layer_masks *masks);
-
-access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
- const access_mask_t access_request,
- struct layer_masks *masks,
- const enum landlock_key_type key_type);
-
#endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y 3/5] landlock: Split struct landlock_domain from struct landlock_ruleset
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 2/5] landlock: Move domain query functions to domain.c Sasha Levin
@ 2026-09-29 13:46 ` Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 4/5] landlock: Split denial logging from audit into common framework Sasha Levin
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-09-29 13:46 UTC (permalink / raw)
To: stable
Cc: Mickaël Salaün, Günther Noack, Tingmao Wang,
Sasha Levin
From: Mickaël Salaün <mic@digikod.net>
[ Upstream commit 38411604162e6abe02741d3c364a4df46d6f612f ]
Switch all domain users to the new struct landlock_domain type
introduced by a previous commit, eliminating the conflation between
mutable rulesets and immutable domains. landlock_merge_ruleset() now
returns and allocates a struct landlock_domain, and the merge and
inherit helpers move next to it; the former static insert_rule() is
exported as landlock_store_rule() for its new caller across the
translation-unit boundary.
The merge destination is now a private struct landlock_domain still
under construction (owned by the calling thread, not yet shared), so the
merge and inherit helpers lock only the source ruleset: the previous
lock of both destination and source collapses to a single
mutex_lock(&src->lock).
Rename the per-layer access-mask field from access_masks to
handled_masks, naming it by the role it plays (the rights each layer
handles) rather than by its type, paralleling the struct access_masks
quiet_masks field. Drop the now domain-only fields (hierarchy,
work_free, num_layers) from struct landlock_ruleset.
The new struct landlock_domain field in cred.h pulls in domain.h, which
includes audit.h, which previously included cred.h, forming an include
cycle. Break it by having audit.h forward-declare the struct
landlock_cred_security and struct landlock_hierarchy it uses instead of
including cred.h.
Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Link: https://patch.msgid.link/20260811094338.288094-4-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Stable-dep-of: c4e941bb7654 ("landlock: Work around gcc-16 -Wuninitialized warning")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/landlock/access.h | 2 +-
security/landlock/audit.c | 12 +-
security/landlock/audit.h | 4 +-
security/landlock/cred.c | 6 +-
security/landlock/cred.h | 21 ++-
security/landlock/domain.c | 258 +++++++++++++++++++++++++-
security/landlock/domain.h | 43 ++++-
security/landlock/fs.c | 25 ++-
security/landlock/net.c | 3 +-
security/landlock/ruleset.c | 338 +++++------------------------------
security/landlock/ruleset.h | 141 +++------------
security/landlock/syscalls.c | 10 +-
security/landlock/task.c | 20 +--
13 files changed, 403 insertions(+), 480 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5f..1a8227a709d93 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -19,7 +19,7 @@
/*
* All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
+ * by a ruleset/layer. This must be ORed with all domain->handled_masks[]
* entries when we need to get the absolute handled access masks, see
* landlock_upgrade_handled_access_masks().
*/
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 50536c5685262..7cb00619a741d 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -138,7 +138,7 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
}
static struct landlock_hierarchy *
-get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
+get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
{
struct landlock_hierarchy *hierarchy = domain->hierarchy;
ssize_t i;
@@ -171,7 +171,7 @@ static void test_get_hierarchy(struct kunit *const test)
.parent = &dom1_hierarchy,
.id = 30,
};
- struct landlock_ruleset dom2 = {
+ struct landlock_domain dom2 = {
.hierarchy = &dom2_hierarchy,
.num_layers = 3,
};
@@ -185,7 +185,7 @@ static void test_get_hierarchy(struct kunit *const test)
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
/* Get the youngest layer that denied the access_request. */
-static size_t get_denied_layer(const struct landlock_ruleset *const domain,
+static size_t get_denied_layer(const struct landlock_domain *const domain,
access_mask_t *const access_request,
const struct layer_masks *masks)
{
@@ -205,7 +205,7 @@ static size_t get_denied_layer(const struct landlock_ruleset *const domain,
static void test_get_denied_layer(struct kunit *const test)
{
- const struct landlock_ruleset dom = {
+ const struct landlock_domain dom = {
.num_layers = 5,
};
const struct layer_masks masks = {
@@ -682,8 +682,8 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
* Only domains which previously appeared in the audit logs are logged again.
* This is useful to know when a domain will never show again in the audit log.
*
- * Called in a work queue scheduled by landlock_put_ruleset_deferred() called
- * by hook_cred_free().
+ * Called in a work queue scheduled by landlock_put_domain_deferred() called by
+ * hook_cred_free().
*/
void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
{
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 620f8a24291d5..4617a855712c4 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -12,7 +12,9 @@
#include <linux/lsm_audit.h>
#include "access.h"
-#include "cred.h"
+
+struct landlock_cred_security;
+struct landlock_hierarchy;
enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index cc419de75cd6b..58b544993db49 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -22,7 +22,7 @@ static void hook_cred_transfer(struct cred *const new,
const struct landlock_cred_security *const old_llcred =
landlock_cred(old);
- landlock_get_ruleset(old_llcred->domain);
+ landlock_get_domain(old_llcred->domain);
*landlock_cred(new) = *old_llcred;
}
@@ -35,10 +35,10 @@ static int hook_cred_prepare(struct cred *const new,
static void hook_cred_free(struct cred *const cred)
{
- struct landlock_ruleset *const dom = landlock_cred(cred)->domain;
+ struct landlock_domain *const dom = landlock_cred(cred)->domain;
if (dom)
- landlock_put_ruleset_deferred(dom);
+ landlock_put_domain_deferred(dom);
}
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4b..e7830cfed0416 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -16,6 +16,7 @@
#include <linux/rcupdate.h>
#include "access.h"
+#include "domain.h"
#include "limits.h"
#include "ruleset.h"
#include "setup.h"
@@ -31,9 +32,9 @@
*/
struct landlock_cred_security {
/**
- * @domain: Immutable ruleset enforced on a task.
+ * @domain: Immutable domain enforced on a task.
*/
- struct landlock_ruleset *domain;
+ struct landlock_domain *domain;
#ifdef CONFIG_AUDIT
/**
@@ -70,22 +71,20 @@ landlock_cred(const struct cred *cred)
static inline void landlock_cred_copy(struct landlock_cred_security *dst,
const struct landlock_cred_security *src)
{
- landlock_put_ruleset(dst->domain);
+ landlock_put_domain(dst->domain);
*dst = *src;
- landlock_get_ruleset(src->domain);
+ landlock_get_domain(src->domain);
}
-static inline struct landlock_ruleset *landlock_get_current_domain(void)
+static inline struct landlock_domain *landlock_get_current_domain(void)
{
return landlock_cred(current_cred())->domain;
}
-/*
- * The call needs to come from an RCU read-side critical section.
- */
-static inline const struct landlock_ruleset *
+/* The call needs to come from an RCU read-side critical section. */
+static inline const struct landlock_domain *
landlock_get_task_domain(const struct task_struct *const task)
{
return landlock_cred(__task_cred(task))->domain;
@@ -126,7 +125,7 @@ landlock_get_applicable_subject(const struct cred *const cred,
const union access_masks_all masks_all = {
.masks = masks,
};
- const struct landlock_ruleset *domain;
+ const struct landlock_domain *domain;
ssize_t layer_level;
if (!cred)
@@ -139,7 +138,7 @@ landlock_get_applicable_subject(const struct cred *const cred,
for (layer_level = domain->num_layers - 1; layer_level >= 0;
layer_level--) {
union access_masks_all layer = {
- .masks = domain->access_masks[layer_level],
+ .masks = domain->handled_masks[layer_level],
};
if (layer.all & masks_all.all) {
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 3068576d14eae..2fd8b8532c8a8 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -15,7 +15,9 @@
#include <linux/cred.h>
#include <linux/err.h>
#include <linux/file.h>
+#include <linux/lockdep.h>
#include <linux/mm.h>
+#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/path.h>
#include <linux/pid.h>
@@ -34,6 +36,36 @@
#include "limits.h"
#include "ruleset.h"
+static void build_check_domain(void)
+{
+ const struct landlock_domain domain = {
+ .num_layers = ~0,
+ };
+
+ BUILD_BUG_ON(domain.num_layers < LANDLOCK_MAX_NUM_LAYERS);
+}
+
+static struct landlock_domain *create_domain(const u32 num_layers)
+{
+ struct landlock_domain *new_domain;
+
+ build_check_domain();
+ new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers,
+ GFP_KERNEL_ACCOUNT);
+ if (!new_domain)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&new_domain->usage, 1);
+ new_domain->rules.root_inode = RB_ROOT;
+
+#if IS_ENABLED(CONFIG_INET)
+ new_domain->rules.root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ new_domain->num_layers = num_layers;
+ return new_domain;
+}
+
static void free_domain(struct landlock_domain *const domain)
{
might_sleep();
@@ -67,13 +99,13 @@ void landlock_put_domain_deferred(struct landlock_domain *const domain)
/* The returned access has the same lifetime as the domain. */
const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
+landlock_find_rule(const struct landlock_domain *const domain,
const struct landlock_id id)
{
const struct rb_root *root;
const struct rb_node *node;
- root = landlock_get_rule_root((struct landlock_rules *)&ruleset->rules,
+ root = landlock_get_rule_root((struct landlock_rules *)&domain->rules,
id.type);
if (IS_ERR(root))
return NULL;
@@ -155,7 +187,7 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
}
typedef access_mask_t
-get_access_mask_t(const struct landlock_ruleset *const ruleset,
+get_access_mask_t(const struct landlock_domain *const domain,
const u16 layer_level);
/**
@@ -174,7 +206,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
* any of the active layers in @domain.
*/
access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+landlock_init_layer_masks(const struct landlock_domain *const domain,
const access_mask_t access_request,
struct layer_masks *const masks,
const enum landlock_key_type key_type)
@@ -222,6 +254,224 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
return handled_accesses;
}
+static int merge_tree(struct landlock_domain *const dst,
+ struct landlock_ruleset *const src,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *src_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&src->lock);
+
+ src_root = landlock_get_rule_root(&src->rules, key_type);
+ if (IS_ERR(src_root))
+ return PTR_ERR(src_root);
+
+ /* Merges the @src tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
+ node) {
+ struct landlock_layer layers[] = { {
+ .level = dst->num_layers,
+ } };
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+ return -EINVAL;
+
+ layers[0].access = walker_rule->layers[0].access;
+ layers[0].flags = walker_rule->layers[0].flags;
+
+ err = landlock_store_rule(&dst->rules, id, &layers,
+ ARRAY_SIZE(layers));
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int merge_ruleset(struct landlock_domain *const dst,
+ struct landlock_ruleset *const src)
+{
+ int err = 0;
+
+ might_sleep();
+ /* Should already be checked by landlock_merge_ruleset() */
+ if (WARN_ON_ONCE(!src))
+ return 0;
+ /* Only merge into a domain. */
+ if (WARN_ON_ONCE(!dst || !dst->hierarchy))
+ return -EINVAL;
+
+ mutex_lock(&src->lock);
+
+ /* Stacks the new layer. */
+ if (WARN_ON_ONCE(dst->num_layers < 1)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ dst->handled_masks[dst->num_layers - 1] =
+ landlock_upgrade_handled_access_masks(src->handled_masks);
+
+ /* Merges the @src inode tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+ if (err)
+ goto out_unlock;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Merges the @src network port tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+out_unlock:
+ mutex_unlock(&src->lock);
+ return err;
+}
+
+static int inherit_tree(struct landlock_domain *const parent,
+ struct landlock_domain *const child,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *parent_root;
+ int err = 0;
+
+ might_sleep();
+
+ parent_root = landlock_get_rule_root(&parent->rules, key_type);
+ if (IS_ERR(parent_root))
+ return PTR_ERR(parent_root);
+
+ /* Copies the @parent inode or network tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+ parent_root, node) {
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ err = landlock_store_rule(&child->rules, id,
+ &walker_rule->layers,
+ walker_rule->num_layers);
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int inherit_ruleset(struct landlock_domain *const parent,
+ struct landlock_domain *const child)
+{
+ int err = 0;
+
+ might_sleep();
+ if (!parent)
+ return 0;
+
+ /* Copies the @parent inode tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+ if (err)
+ return err;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Copies the @parent network port tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ return err;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ if (WARN_ON_ONCE(child->num_layers <= parent->num_layers))
+ return -EINVAL;
+
+ /*
+ * Copies the parent layer stack and leaves a space for the new layer.
+ */
+ memcpy(child->handled_masks, parent->handled_masks,
+ flex_array_size(parent, handled_masks, parent->num_layers));
+
+ if (WARN_ON_ONCE(!parent->hierarchy))
+ return -EINVAL;
+
+ landlock_get_hierarchy(parent->hierarchy);
+ child->hierarchy->parent = parent->hierarchy;
+
+ return 0;
+}
+
+/**
+ * landlock_merge_ruleset - Merge a ruleset with a domain
+ *
+ * @parent: Parent domain.
+ * @ruleset: New ruleset to be merged.
+ *
+ * The current task is requesting to be restricted. The subjective credentials
+ * must not be in an overridden state. cf. landlock_init_hierarchy_log().
+ *
+ * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR() on
+ * failure. If @parent is NULL, the new domain duplicates @ruleset.
+ */
+struct landlock_domain *
+landlock_merge_ruleset(struct landlock_domain *const parent,
+ struct landlock_ruleset *const ruleset)
+{
+ struct landlock_domain *new_dom __free(landlock_put_domain) = NULL;
+ u32 num_layers;
+ 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);
+ num_layers = parent->num_layers + 1;
+ } else {
+ num_layers = 1;
+ }
+
+ /* Creates a new domain... */
+ new_dom = create_domain(num_layers);
+ if (IS_ERR(new_dom))
+ return new_dom;
+
+ new_dom->hierarchy =
+ kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
+ if (!new_dom->hierarchy)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&new_dom->hierarchy->usage, 1);
+
+ /* ...as a child of @parent... */
+ err = inherit_ruleset(parent, new_dom);
+ if (err)
+ return ERR_PTR(err);
+
+ /* ...and including @ruleset. */
+ err = merge_ruleset(new_dom, ruleset);
+ if (err)
+ return ERR_PTR(err);
+
+ err = landlock_init_hierarchy_log(new_dom->hierarchy);
+ if (err)
+ return ERR_PTR(err);
+
+#ifdef CONFIG_AUDIT
+ new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
+#endif /* CONFIG_AUDIT */
+
+ return no_free_ptr(new_dom);
+}
+
#ifdef CONFIG_AUDIT
/**
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 315f72b0b34df..7cd6154dd4e4c 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -203,7 +203,7 @@ struct landlock_domain {
* @work_free: Enables to free a domain within a lockless
* section. This is only used by landlock_put_domain_deferred()
* when @usage reaches zero. The fields @usage, @num_layers and
- * @access_masks are then unused.
+ * @handled_masks are then unused.
*/
struct work_struct work_free;
struct {
@@ -219,7 +219,7 @@ struct landlock_domain {
*/
u32 num_layers;
/**
- * @access_masks: Contains the subset of filesystem and
+ * @handled_masks: Contains the subset of filesystem and
* network actions that are restricted by a domain. A
* domain saves all layers of merged rulesets in a stack
* (FAM), starting from the first layer to the last one.
@@ -229,28 +229,51 @@ struct landlock_domain {
* overlapping access rights. These layers are set once
* and never changed for the lifetime of the domain.
*/
- struct access_masks access_masks[];
+ struct access_masks handled_masks[];
};
};
};
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ /* Handles all initially denied by default access rights. */
+ return domain->handled_masks[layer_level].fs |
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+}
+
+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ return domain->handled_masks[layer_level].net;
+}
+
+static inline access_mask_t
+landlock_get_scope_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ return domain->handled_masks[layer_level].scope;
+}
+
/**
* landlock_union_access_masks - Return all access rights handled in the
* domain
*
- * @domain: Landlock ruleset (used as a domain)
+ * @domain: Landlock domain
*
* Return: An access_masks result of the OR of all the domain's access masks.
*/
static inline struct access_masks
-landlock_union_access_masks(const struct landlock_ruleset *const domain)
+landlock_union_access_masks(const struct landlock_domain *const domain)
{
union access_masks_all matches = {};
size_t layer_level;
for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
union access_masks_all layer = {
- .masks = domain->access_masks[layer_level],
+ .masks = domain->handled_masks[layer_level],
};
matches.all |= layer.all;
@@ -265,15 +288,19 @@ 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_domain *
+landlock_merge_ruleset(struct landlock_domain *const parent,
+ struct landlock_ruleset *const ruleset);
+
const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
+landlock_find_rule(const struct landlock_domain *const domain,
const struct landlock_id id);
bool landlock_unmask_layers(const struct landlock_rule *const rule,
struct layer_masks *masks);
access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+landlock_init_layer_masks(const struct landlock_domain *const domain,
const access_mask_t access_request,
struct layer_masks *masks,
const enum landlock_key_type key_type);
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f1c9effc93fe4..0c98862ced424 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -337,12 +337,11 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
if (!d_is_dir(path->dentry) &&
!access_mask_subset(access_rights, ACCESS_FILE))
return -EINVAL;
- if (WARN_ON_ONCE(ruleset->num_layers != 1))
- return -EINVAL;
/* Transforms relative access rights to absolute ones. */
access_rights |= LANDLOCK_MASK_ACCESS_FS &
- ~landlock_get_fs_access_mask(ruleset, 0);
+ ~(ruleset->handled_masks.fs |
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED);
id.key.object = get_inode_object(d_backing_inode(path->dentry));
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
@@ -365,7 +364,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
* Returns NULL if no rule is found or if @dentry is negative.
*/
static const struct landlock_rule *
-find_rule(const struct landlock_ruleset *const domain,
+find_rule(const struct landlock_domain *const domain,
const struct dentry *const dentry)
{
const struct landlock_rule *rule;
@@ -750,7 +749,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
* Return: True if the access request is granted, false otherwise.
*/
static bool
-is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
+is_access_to_paths_allowed(const struct landlock_domain *const domain,
const struct path *const path,
const access_mask_t access_request_parent1,
struct layer_masks *layer_masks_parent1,
@@ -1051,7 +1050,7 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
* Return: True if all the domain access rights are allowed for @dir, false if
* the walk reached @mnt_root.
*/
-static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
+static bool collect_domain_accesses(const struct landlock_domain *const domain,
const struct dentry *const mnt_root,
struct dentry *dir,
struct layer_masks *layer_masks_dom)
@@ -1618,8 +1617,8 @@ static int hook_path_truncate(const struct path *const path)
* @masks: Layer access masks to unmask
* @access: Access bits that control scoping
*/
-static void unmask_scoped_access(const struct landlock_ruleset *const client,
- const struct landlock_ruleset *const server,
+static void unmask_scoped_access(const struct landlock_domain *const client,
+ const struct landlock_domain *const server,
struct layer_masks *const masks,
const access_mask_t access)
{
@@ -1673,7 +1672,7 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
static int hook_unix_find(const struct path *const path, struct sock *other,
int flags)
{
- const struct landlock_ruleset *dom_other;
+ const struct landlock_domain *dom_other;
const struct landlock_cred_security *subject;
struct layer_masks layer_masks;
struct landlock_request request = {};
@@ -1968,7 +1967,7 @@ static bool control_current_fowner(struct fown_struct *const fown)
static void hook_file_set_fowner(struct file *file)
{
- struct landlock_ruleset *prev_dom;
+ struct landlock_domain *prev_dom;
struct landlock_cred_security fown_subject = {};
struct pid *prev_tg, *fown_tg = NULL;
size_t fown_layer = 0;
@@ -1981,7 +1980,7 @@ static void hook_file_set_fowner(struct file *file)
landlock_get_applicable_subject(
current_cred(), signal_scope, &fown_layer);
if (new_subject) {
- landlock_get_ruleset(new_subject->domain);
+ landlock_get_domain(new_subject->domain);
fown_subject = *new_subject;
fown_tg = get_pid(task_tgid(current));
}
@@ -1996,14 +1995,14 @@ static void hook_file_set_fowner(struct file *file)
#endif /* CONFIG_AUDIT*/
/* May be called in an RCU read-side critical section. */
- landlock_put_ruleset_deferred(prev_dom);
+ landlock_put_domain_deferred(prev_dom);
put_pid(prev_tg);
}
static void hook_file_free_security(struct file *file)
{
put_pid(landlock_file(file)->fown_tg);
- landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ landlock_put_domain_deferred(landlock_file(file)->fown_subject.domain);
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
diff --git a/security/landlock/net.c b/security/landlock/net.c
index feecc8c77ab4f..7447738ca2e6e 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -33,8 +33,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
/* Transforms relative access rights to absolute ones. */
- access_rights |= LANDLOCK_MASK_ACCESS_NET &
- ~landlock_get_net_access_mask(ruleset, 0);
+ access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->handled_masks.net;
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights, flags);
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 96e65804d2ff4..2b74542ac3d17 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -20,23 +20,28 @@
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
-#include <linux/workqueue.h>
#include <uapi/linux/landlock.h>
#include "access.h"
-#include "domain.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
-static struct landlock_ruleset *create_ruleset(const u32 num_layers)
+struct landlock_ruleset *
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+ const access_mask_t net_access_mask,
+ const access_mask_t scope_mask)
{
struct landlock_ruleset *new_ruleset;
- new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,
- GFP_KERNEL_ACCOUNT);
+ /* Informs about useless ruleset. */
+ if (!fs_access_mask && !net_access_mask && !scope_mask)
+ return ERR_PTR(-ENOMSG);
+
+ new_ruleset = kzalloc_obj(*new_ruleset, GFP_KERNEL_ACCOUNT);
if (!new_ruleset)
return ERR_PTR(-ENOMEM);
+
refcount_set(&new_ruleset->usage, 1);
mutex_init(&new_ruleset->lock);
new_ruleset->rules.root_inode = RB_ROOT;
@@ -45,34 +50,27 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
new_ruleset->rules.root_net_port = RB_ROOT;
#endif /* IS_ENABLED(CONFIG_INET) */
- new_ruleset->num_layers = num_layers;
- /*
- * hierarchy = NULL
- * rules.num_rules = 0
- * access_masks[] = 0
- */
- return new_ruleset;
-}
+ /* Should already be checked in landlock_create_ruleset(). */
+ if (fs_access_mask) {
+ const access_mask_t mask = fs_access_mask &
+ LANDLOCK_MASK_ACCESS_FS;
-struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask,
- const access_mask_t net_access_mask,
- const access_mask_t scope_mask)
-{
- struct landlock_ruleset *new_ruleset;
+ WARN_ON_ONCE(fs_access_mask != mask);
+ new_ruleset->handled_masks.fs |= mask;
+ }
+ if (net_access_mask) {
+ const access_mask_t mask = net_access_mask &
+ LANDLOCK_MASK_ACCESS_NET;
- /* Informs about useless ruleset. */
- if (!fs_access_mask && !net_access_mask && !scope_mask)
- return ERR_PTR(-ENOMSG);
- new_ruleset = create_ruleset(1);
- if (IS_ERR(new_ruleset))
- return new_ruleset;
- if (fs_access_mask)
- landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
- if (net_access_mask)
- landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
- if (scope_mask)
- landlock_add_scope_mask(new_ruleset, scope_mask, 0);
+ WARN_ON_ONCE(net_access_mask != mask);
+ new_ruleset->handled_masks.net |= mask;
+ }
+ if (scope_mask) {
+ const access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
+
+ WARN_ON_ONCE(scope_mask != mask);
+ new_ruleset->handled_masks.scope |= mask;
+ }
return new_ruleset;
}
@@ -129,7 +127,7 @@ create_rule(const struct landlock_id id,
return ERR_PTR(-ENOMEM);
RB_CLEAR_NODE(&new_rule->node);
if (is_object_pointer(id.type)) {
- /* This should have been caught by insert_rule(). */
+ /* This should have been caught by landlock_store_rule(). */
WARN_ON_ONCE(!id.key.object);
landlock_get_object(id.key.object);
}
@@ -145,12 +143,6 @@ create_rule(const struct landlock_id id,
return new_rule;
}
-static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
- const enum landlock_key_type key_type)
-{
- return landlock_get_rule_root(&ruleset->rules, key_type);
-}
-
static void free_rule(struct landlock_rule *const rule,
const enum landlock_key_type key_type)
{
@@ -167,16 +159,12 @@ static void build_check_ruleset(void)
const struct landlock_rules rules = {
.num_rules = ~0,
};
- const struct landlock_ruleset ruleset = {
- .num_layers = ~0,
- };
BUILD_BUG_ON(rules.num_rules < LANDLOCK_MAX_NUM_RULES);
- BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
}
/**
- * insert_rule - Create and insert a rule into the rule storage
+ * landlock_store_rule - Create and insert a rule into the rule storage
*
* @rules: The rule storage to be updated. The caller is responsible for
* any required locking. For rulesets, this means holding
@@ -198,10 +186,10 @@ static void build_check_ruleset(void)
*
* Return: 0 on success, -errno on failure.
*/
-static int insert_rule(struct landlock_rules *const rules,
- const struct landlock_id id,
- const struct landlock_layer (*layers)[],
- const size_t num_layers)
+int landlock_store_rule(struct landlock_rules *const rules,
+ const struct landlock_id id,
+ const struct landlock_layer (*layers)[],
+ const size_t num_layers)
{
struct rb_node **walker_node;
struct rb_node *parent_node = NULL;
@@ -241,7 +229,7 @@ static int insert_rule(struct landlock_rules *const rules,
if ((*layers)[0].level == 0) {
/*
* Extends access rights when the request comes from
- * landlock_add_rule(2), i.e. contained by a ruleset.
+ * landlock_add_rule(2), i.e. @rules is not a domain.
*/
if (WARN_ON_ONCE(this->num_layers != 1))
return -EINVAL;
@@ -303,7 +291,9 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
{
struct landlock_layer layers[] = { {
.access = access,
- /* When @level is zero, insert_rule() extends @ruleset. */
+ /*
+ * When @level is zero, landlock_store_rule() extends @ruleset.
+ */
.level = 0,
.flags = {
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
@@ -312,171 +302,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
build_check_layer();
lockdep_assert_held(&ruleset->lock);
- return insert_rule(&ruleset->rules, id, &layers, ARRAY_SIZE(layers));
-}
-
-static int merge_tree(struct landlock_ruleset *const dst,
- struct landlock_ruleset *const src,
- const enum landlock_key_type key_type)
-{
- struct landlock_rule *walker_rule, *next_rule;
- struct rb_root *src_root;
- int err = 0;
-
- might_sleep();
- lockdep_assert_held(&dst->lock);
- lockdep_assert_held(&src->lock);
-
- src_root = get_root(src, key_type);
- if (IS_ERR(src_root))
- return PTR_ERR(src_root);
-
- /* Merges the @src tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
- node) {
- struct landlock_layer layers[] = { {
- .level = dst->num_layers,
- } };
- const struct landlock_id id = {
- .key = walker_rule->key,
- .type = key_type,
- };
-
- if (WARN_ON_ONCE(walker_rule->num_layers != 1))
- return -EINVAL;
-
- if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
- return -EINVAL;
-
- layers[0].access = walker_rule->layers[0].access;
- layers[0].flags = walker_rule->layers[0].flags;
-
- err = insert_rule(&dst->rules, id, &layers, ARRAY_SIZE(layers));
- if (err)
- return err;
- }
- return err;
-}
-
-static int merge_ruleset(struct landlock_ruleset *const dst,
- struct landlock_ruleset *const src)
-{
- int err = 0;
-
- might_sleep();
- /* Should already be checked by landlock_merge_ruleset() */
- if (WARN_ON_ONCE(!src))
- return 0;
- /* Only merge into a domain. */
- if (WARN_ON_ONCE(!dst || !dst->hierarchy))
- return -EINVAL;
-
- /* Locks @dst first because we are its only owner. */
- mutex_lock(&dst->lock);
- mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
-
- /* Stacks the new layer. */
- if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
- err = -EINVAL;
- goto out_unlock;
- }
- dst->access_masks[dst->num_layers - 1] =
- landlock_upgrade_handled_access_masks(src->access_masks[0]);
-
- /* Merges the @src inode tree. */
- err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
- if (err)
- goto out_unlock;
-
-#if IS_ENABLED(CONFIG_INET)
- /* Merges the @src network port tree. */
- err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
- if (err)
- goto out_unlock;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
-out_unlock:
- mutex_unlock(&src->lock);
- mutex_unlock(&dst->lock);
- return err;
-}
-
-static int inherit_tree(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const child,
- const enum landlock_key_type key_type)
-{
- struct landlock_rule *walker_rule, *next_rule;
- struct rb_root *parent_root;
- int err = 0;
-
- might_sleep();
- lockdep_assert_held(&parent->lock);
- lockdep_assert_held(&child->lock);
-
- parent_root = get_root(parent, key_type);
- if (IS_ERR(parent_root))
- return PTR_ERR(parent_root);
-
- /* Copies the @parent inode or network tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
- parent_root, node) {
- const struct landlock_id id = {
- .key = walker_rule->key,
- .type = key_type,
- };
-
- err = insert_rule(&child->rules, id, &walker_rule->layers,
- walker_rule->num_layers);
- if (err)
- return err;
- }
- return err;
-}
-
-static int inherit_ruleset(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const child)
-{
- int err = 0;
-
- might_sleep();
- if (!parent)
- return 0;
-
- /* Locks @child first because we are its only owner. */
- mutex_lock(&child->lock);
- mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
-
- /* Copies the @parent inode tree. */
- err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
- if (err)
- goto out_unlock;
-
-#if IS_ENABLED(CONFIG_INET)
- /* Copies the @parent network port tree. */
- err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
- if (err)
- goto out_unlock;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
- err = -EINVAL;
- goto out_unlock;
- }
- /* Copies the parent layer stack and leaves a space for the new layer. */
- memcpy(child->access_masks, parent->access_masks,
- flex_array_size(parent, access_masks, parent->num_layers));
-
- if (WARN_ON_ONCE(!parent->hierarchy)) {
- err = -EINVAL;
- goto out_unlock;
- }
- landlock_get_hierarchy(parent->hierarchy);
- child->hierarchy->parent = parent->hierarchy;
-
-out_unlock:
- mutex_unlock(&parent->lock);
- mutex_unlock(&child->lock);
- return err;
+ return landlock_store_rule(&ruleset->rules, id, &layers,
+ ARRAY_SIZE(layers));
}
void landlock_free_rules(struct landlock_rules *const rules)
@@ -499,7 +326,6 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
{
might_sleep();
landlock_free_rules(&ruleset->rules);
- landlock_put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
@@ -509,85 +335,3 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset)
if (ruleset && refcount_dec_and_test(&ruleset->usage))
free_ruleset(ruleset);
}
-
-static void free_ruleset_work(struct work_struct *const work)
-{
- struct landlock_ruleset *ruleset;
-
- ruleset = container_of(work, struct landlock_ruleset, work_free);
- free_ruleset(ruleset);
-}
-
-/* Only called by hook_cred_free(). */
-void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
-{
- if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
- INIT_WORK(&ruleset->work_free, free_ruleset_work);
- schedule_work(&ruleset->work_free);
- }
-}
-
-/**
- * landlock_merge_ruleset - Merge a ruleset with a domain
- *
- * @parent: Parent domain.
- * @ruleset: New ruleset to be merged.
- *
- * The current task is requesting to be restricted. The subjective credentials
- * must not be in an overridden state. cf. landlock_init_hierarchy_log().
- *
- * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR()
- * on failure. If @parent is NULL, the new domain duplicates @ruleset.
- */
-struct landlock_ruleset *
-landlock_merge_ruleset(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const ruleset)
-{
- struct landlock_ruleset *new_dom __free(landlock_put_ruleset) = NULL;
- u32 num_layers;
- int err;
-
- might_sleep();
- if (WARN_ON_ONCE(!ruleset || parent == ruleset))
- return ERR_PTR(-EINVAL);
-
- if (parent) {
- if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
- return ERR_PTR(-E2BIG);
- num_layers = parent->num_layers + 1;
- } else {
- num_layers = 1;
- }
-
- /* Creates a new domain... */
- new_dom = create_ruleset(num_layers);
- if (IS_ERR(new_dom))
- return new_dom;
-
- new_dom->hierarchy =
- kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
- if (!new_dom->hierarchy)
- return ERR_PTR(-ENOMEM);
-
- refcount_set(&new_dom->hierarchy->usage, 1);
-
- /* ...as a child of @parent... */
- err = inherit_ruleset(parent, new_dom);
- if (err)
- return ERR_PTR(err);
-
- /* ...and including @ruleset. */
- err = merge_ruleset(new_dom, ruleset);
- if (err)
- return ERR_PTR(err);
-
- err = landlock_init_hierarchy_log(new_dom->hierarchy);
- if (err)
- return ERR_PTR(err);
-
-#ifdef CONFIG_AUDIT
- new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
-#endif /* CONFIG_AUDIT */
-
- return no_free_ptr(new_dom);
-}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f9486c06b7ec3..3a9fc39e947c3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -14,14 +14,11 @@
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
-#include <linux/workqueue.h>
#include "access.h"
#include "limits.h"
#include "object.h"
-struct landlock_hierarchy;
-
/**
* struct landlock_layer - Access rights for a given layer
*/
@@ -158,60 +155,26 @@ struct landlock_ruleset {
* @rules: Red-black tree storage for rules.
*/
struct landlock_rules rules;
-
/**
- * @hierarchy: Enables hierarchy identification even when a parent
- * domain vanishes. This is needed for the ptrace protection.
+ * @lock: Protects against concurrent modifications of @rules, if @usage
+ * is greater than zero.
*/
- struct landlock_hierarchy *hierarchy;
- union {
- /**
- * @work_free: Enables to free a ruleset within a lockless
- * section. This is only used by
- * landlock_put_ruleset_deferred() when @usage reaches zero.
- * The fields @lock, @usage, @num_layers, @quiet_masks and
- * @access_masks are then unused.
- */
- struct work_struct work_free;
- struct {
- /**
- * @lock: Protects against concurrent modifications of
- * @root, if @usage is greater than zero.
- */
- struct mutex lock;
- /**
- * @usage: Number of processes (i.e. domains) or file
- * descriptors referencing this ruleset.
- */
- refcount_t usage;
- /**
- * @num_layers: Number of layers that are used in this
- * ruleset. This enables to check that all the layers
- * allow an access request. A value of 0 identifies a
- * non-merged ruleset (i.e. not a domain).
- */
- u32 num_layers;
- /**
- * @quiet_masks: Stores the quiet flags for an unmerged
- * ruleset. For a merged domain, this is stored in each
- * layer's struct landlock_hierarchy instead.
- */
- struct access_masks quiet_masks;
- /**
- * @access_masks: Contains the subset of filesystem and
- * network actions that are restricted by a ruleset.
- * A domain saves all layers of merged rulesets in a
- * stack (FAM), starting from the first layer to the
- * last one. These layers are used when merging
- * rulesets, for user space backward compatibility
- * (i.e. future-proof), and to properly handle merged
- * rulesets without overlapping access rights. These
- * layers are set once and never changed for the
- * lifetime of the ruleset.
- */
- struct access_masks access_masks[];
- };
- };
+ struct mutex lock;
+ /**
+ * @usage: Number of file descriptors referencing this ruleset.
+ */
+ refcount_t usage;
+ /**
+ * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a
+ * merged domain, this is stored in each layer's struct
+ * landlock_hierarchy instead.
+ */
+ struct access_masks quiet_masks;
+ /**
+ * @handled_masks: Contains the subset of filesystem and network actions
+ * that are handled by this ruleset.
+ */
+ struct access_masks handled_masks;
};
struct landlock_ruleset *
@@ -220,7 +183,6 @@ landlock_create_ruleset(const access_mask_t access_mask_fs,
const access_mask_t scope_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
-void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
if (!IS_ERR_OR_NULL(_T)) landlock_put_ruleset(_T))
@@ -229,11 +191,12 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
const access_mask_t access, const u32 flags);
-void landlock_free_rules(struct landlock_rules *const rules);
+int landlock_store_rule(struct landlock_rules *const rules,
+ const struct landlock_id id,
+ const struct landlock_layer (*layers)[],
+ const size_t num_layers);
-struct landlock_ruleset *
-landlock_merge_ruleset(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const ruleset);
+void landlock_free_rules(struct landlock_rules *const rules);
/**
* landlock_get_rule_root - Get the root of a rule tree by key type
@@ -268,62 +231,4 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
-static inline void
-landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t fs_access_mask,
- const u16 layer_level)
-{
- access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(fs_access_mask != fs_mask);
- ruleset->access_masks[layer_level].fs |= fs_mask;
-}
-
-static inline void
-landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t net_access_mask,
- const u16 layer_level)
-{
- access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(net_access_mask != net_mask);
- ruleset->access_masks[layer_level].net |= net_mask;
-}
-
-static inline void
-landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t scope_mask, const u16 layer_level)
-{
- access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(scope_mask != mask);
- ruleset->access_masks[layer_level].scope |= mask;
-}
-
-static inline access_mask_t
-landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- /* Handles all initially denied by default access rights. */
- return ruleset->access_masks[layer_level].fs |
- _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
-}
-
-static inline access_mask_t
-landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- return ruleset->access_masks[layer_level].net;
-}
-
-static inline access_mask_t
-landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- return ruleset->access_masks[layer_level].scope;
-}
-
#endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f5..fe8505dc0ba52 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -308,8 +308,6 @@ static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
if (!(fd_file(ruleset_f)->f_mode & mode))
return ERR_PTR(-EPERM);
ruleset = fd_file(ruleset_f)->private_data;
- if (WARN_ON_ONCE(ruleset->num_layers != 1))
- return ERR_PTR(-EINVAL);
landlock_get_ruleset(ruleset);
return ruleset;
}
@@ -367,7 +365,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
- mask = ruleset->access_masks[0].fs;
+ mask = ruleset->handled_masks.fs;
if ((path_beneath_attr.allowed_access | mask) != mask)
return -EINVAL;
@@ -408,7 +406,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
- mask = landlock_get_net_access_mask(ruleset, 0);
+ mask = ruleset->handled_masks.net;
if ((net_port_attr.allowed_access | mask) != mask)
return -EINVAL;
@@ -595,7 +593,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
* manipulating the current credentials because they are
* dedicated per thread.
*/
- struct landlock_ruleset *const new_dom =
+ struct landlock_domain *const new_dom =
landlock_merge_ruleset(new_llcred->domain, ruleset);
if (IS_ERR(new_dom)) {
abort_creds(new_cred);
@@ -610,7 +608,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
#endif /* CONFIG_AUDIT */
/* Replaces the old (prepared) domain. */
- landlock_put_ruleset(new_llcred->domain);
+ landlock_put_domain(new_llcred->domain);
new_llcred->domain = new_dom;
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 360d226d0f51d..40e6bfa4bc759 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -41,8 +41,8 @@
* Return: True if @parent is an ancestor of or equal to @child, false
* otherwise.
*/
-static bool domain_scope_le(const struct landlock_ruleset *const parent,
- const struct landlock_ruleset *const child)
+static bool domain_scope_le(const struct landlock_domain *const parent,
+ const struct landlock_domain *const child)
{
const struct landlock_hierarchy *walker;
@@ -63,8 +63,8 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent,
return false;
}
-static int domain_ptrace(const struct landlock_ruleset *const parent,
- const struct landlock_ruleset *const child)
+static int domain_ptrace(const struct landlock_domain *const parent,
+ const struct landlock_domain *const child)
{
if (domain_scope_le(parent, child))
return 0;
@@ -96,7 +96,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
return 0;
scoped_guard(rcu) {
- const struct landlock_ruleset *const child_dom =
+ const struct landlock_domain *const child_dom =
landlock_get_task_domain(child);
err = domain_ptrace(parent_subject->domain, child_dom);
}
@@ -135,7 +135,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
static int hook_ptrace_traceme(struct task_struct *const parent)
{
const struct landlock_cred_security *parent_subject;
- const struct landlock_ruleset *child_dom;
+ const struct landlock_domain *child_dom;
int err;
child_dom = landlock_get_current_domain();
@@ -176,8 +176,8 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
* Return: True if @server is in a different domain from @client and @client
* is scoped to access @server (i.e. access should be denied), false otherwise.
*/
-static bool domain_is_scoped(const struct landlock_ruleset *const client,
- const struct landlock_ruleset *const server,
+static bool domain_is_scoped(const struct landlock_domain *const client,
+ const struct landlock_domain *const server,
access_mask_t scope)
{
int client_layer, server_layer;
@@ -236,9 +236,9 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
}
static bool sock_is_scoped(struct sock *const other,
- const struct landlock_ruleset *const domain)
+ const struct landlock_domain *const domain)
{
- const struct landlock_ruleset *dom_other;
+ const struct landlock_domain *dom_other;
/* The credentials will not change. */
lockdep_assert_held(&unix_sk(other)->lock);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y 4/5] landlock: Split denial logging from audit into common framework
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 2/5] landlock: Move domain query functions to domain.c Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 3/5] landlock: Split struct landlock_domain from struct landlock_ruleset Sasha Levin
@ 2026-09-29 13:46 ` Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 5/5] landlock: Work around gcc-16 -Wuninitialized warning Sasha Levin
2026-09-29 18:29 ` [PATCH 7.2.y v2] " Mickaël Salaün
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-09-29 13:46 UTC (permalink / raw)
To: stable; +Cc: Mickaël Salaün, Günther Noack, Sasha Levin
From: Mickaël Salaün <mic@digikod.net>
[ Upstream commit e761a88cedeb46de17b6d9e8c7c41c7671e83a6b ]
Tracepoint emission requires the denial framework (layer identification,
request validation) without depending on CONFIG_AUDIT. Separate the
denial logging infrastructure from the audit-specific code by
introducing a common log framework.
Create CONFIG_SECURITY_LANDLOCK_LOG, enabled by default when
CONFIG_AUDIT is set; a following commit extends it to CONFIG_TRACEPOINTS
when the first tracepoint consumer is added. Move the common framework
(the request types, the layer identification and request validation, and
the landlock_log_denial() and landlock_log_free_domain() entry points)
into log.c and log.h, and keep the audit-specific record formatting in
audit.c. log.o is built for CONFIG_SECURITY_LANDLOCK_LOG and audit.o for
CONFIG_AUDIT, so the common framework is available to a tracepoints-only
build. The entry points dispatch to no-op static inline audit stubs
without CONFIG_AUDIT, so the call sites stay unconditional. Rename the
former landlock_log_drop_domain() to landlock_log_free_domain() to match
the landlock_free_domain tracepoint added in a following commit.
landlock_log_denial() counts denials even without audit, so its
declaration and no-op stub are guarded by CONFIG_SECURITY_LANDLOCK_LOG,
not CONFIG_AUDIT; a CONFIG_AUDIT guard would expose the stub and clash
with log.c's definition in a tracepoints-only build.
Widen the ID allocation (id.o and the landlock_init_id() /
landlock_get_id_range() declarations) and the log-state representation
(the domain_exec and log_subdomains_off credential fields, the
landlock_hierarchy log fields, and the code that maintains them) from
CONFIG_AUDIT to CONFIG_SECURITY_LANDLOCK_LOG, so each field and its
writer share one guard and are available to tracing without audit
support.
Widen the denial-path state that feeds the per-denial logging decision
the same way, so the "logged" verdict is computed identically whether or
not CONFIG_AUDIT is set. Widening fown_layer is what keeps the
file-owner-signal path valid without audit: otherwise
hook_file_send_sigiotask() would leave layer_plus_one at zero, tripping
the is_valid_request() canary and dropping the LANDLOCK_SCOPE_SIGNAL
denial from tracing.
The ruleset-level quiet_masks stays on no CONFIG guard: it is builder
state validated and stored from user input, kept available so
LANDLOCK_ADD_RULE_QUIET flags are accepted and ignored, not rejected,
when CONFIG_SECURITY_LANDLOCK_LOG is disabled.
Cc: Günther Noack <gnoack@google.com>
Link: https://patch.msgid.link/20260811094338.288094-5-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Stable-dep-of: c4e941bb7654 ("landlock: Work around gcc-16 -Wuninitialized warning")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/landlock/Kconfig | 5 +
security/landlock/Makefile | 6 +-
security/landlock/access.h | 4 +-
security/landlock/audit.c | 485 ++-------------------------------
security/landlock/audit.h | 61 ++---
security/landlock/cred.c | 8 +-
security/landlock/cred.h | 8 +-
security/landlock/domain.c | 20 +-
security/landlock/domain.h | 14 +-
security/landlock/fs.c | 27 +-
security/landlock/fs.h | 8 +-
security/landlock/id.h | 6 +-
security/landlock/log.c | 502 +++++++++++++++++++++++++++++++++++
security/landlock/log.h | 77 ++++++
security/landlock/net.c | 2 +-
security/landlock/syscalls.c | 12 +-
security/landlock/task.c | 6 +-
17 files changed, 681 insertions(+), 570 deletions(-)
create mode 100644 security/landlock/log.c
create mode 100644 security/landlock/log.h
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index 3f1493402052e..b4e7f0e9ba9b2 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -21,6 +21,11 @@ config SECURITY_LANDLOCK
you should also prepend "landlock," to the content of CONFIG_LSM to
enable Landlock at boot time.
+config SECURITY_LANDLOCK_LOG
+ bool
+ depends on SECURITY_LANDLOCK
+ default y if AUDIT
+
config SECURITY_LANDLOCK_KUNIT_TEST
bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS
depends on KUNIT=y
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 23e13644916f7..2462e6c5921ed 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -13,6 +13,8 @@ landlock-y := \
landlock-$(CONFIG_INET) += net.o
-landlock-$(CONFIG_AUDIT) += \
+landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \
id.o \
- audit.o
+ log.o
+
+landlock-$(CONFIG_AUDIT) += audit.o
diff --git a/security/landlock/access.h b/security/landlock/access.h
index 1a8227a709d93..bbbb41f41147c 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -74,13 +74,13 @@ struct layer_mask {
* @access: The unfulfilled access rights for this layer.
*/
access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @quiet: Whether we have encountered a rule with the quiet flag for
* this layer. Used to control logging.
*/
access_mask_t quiet : 1;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
} __packed __aligned(sizeof(access_mask_t));
/*
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 7cb00619a741d..dfa1e5a64aacd 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -5,7 +5,6 @@
* Copyright © 2023-2025 Microsoft Corporation
*/
-#include <kunit/test.h>
#include <linux/audit.h>
#include <linux/bitops.h>
#include <linux/lsm_audit.h>
@@ -18,7 +17,7 @@
#include "cred.h"
#include "domain.h"
#include "limits.h"
-#include "ruleset.h"
+#include "log.h"
static const char *const fs_access_strings[] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
@@ -137,393 +136,6 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
}
-static struct landlock_hierarchy *
-get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
-{
- struct landlock_hierarchy *hierarchy = domain->hierarchy;
- ssize_t i;
-
- if (WARN_ON_ONCE(layer >= domain->num_layers))
- return hierarchy;
-
- for (i = domain->num_layers - 1; i > layer; i--) {
- if (WARN_ON_ONCE(!hierarchy->parent))
- break;
-
- hierarchy = hierarchy->parent;
- }
-
- return hierarchy;
-}
-
-#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
-
-static void test_get_hierarchy(struct kunit *const test)
-{
- struct landlock_hierarchy dom0_hierarchy = {
- .id = 10,
- };
- struct landlock_hierarchy dom1_hierarchy = {
- .parent = &dom0_hierarchy,
- .id = 20,
- };
- struct landlock_hierarchy dom2_hierarchy = {
- .parent = &dom1_hierarchy,
- .id = 30,
- };
- struct landlock_domain dom2 = {
- .hierarchy = &dom2_hierarchy,
- .num_layers = 3,
- };
-
- KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
- KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
- KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
- /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
-}
-
-#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
-
-/* Get the youngest layer that denied the access_request. */
-static size_t get_denied_layer(const struct landlock_domain *const domain,
- access_mask_t *const access_request,
- const struct layer_masks *masks)
-{
- for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
- if (masks->layers[i].access & *access_request) {
- *access_request &= masks->layers[i].access;
- return i;
- }
- }
-
- /* Not found - fall back to default values */
- *access_request = 0;
- return domain->num_layers - 1;
-}
-
-#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
-
-static void test_get_denied_layer(struct kunit *const test)
-{
- const struct landlock_domain dom = {
- .num_layers = 5,
- };
- const struct layer_masks masks = {
- .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
- };
- access_mask_t access;
-
- access = LANDLOCK_ACCESS_FS_EXECUTE;
- KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
-
- access = LANDLOCK_ACCESS_FS_READ_FILE;
- KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
-
- access = LANDLOCK_ACCESS_FS_READ_DIR;
- KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
-
- access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
- KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access,
- LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_READ_DIR);
-
- access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
- KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
-
- access = LANDLOCK_ACCESS_FS_WRITE_FILE;
- KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
- KUNIT_EXPECT_EQ(test, access, 0);
-}
-
-#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
-
-static size_t
-get_layer_from_deny_masks(access_mask_t *const access_request,
- const access_mask_t all_existing_optional_access,
- const deny_masks_t deny_masks,
- optional_access_t quiet_optional_accesses,
- bool *quiet)
-{
- const unsigned long access_opt = all_existing_optional_access;
- const unsigned long access_req = *access_request;
- access_mask_t missing = 0;
- size_t youngest_layer = 0;
- size_t access_index = 0;
- unsigned long access_bit;
- bool should_quiet = false;
-
- /* This will require change with new object types. */
- WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
-
- for_each_set_bit(access_bit, &access_opt,
- BITS_PER_TYPE(access_mask_t)) {
- if (access_req & BIT(access_bit)) {
- const size_t layer =
- (deny_masks >>
- (access_index *
- HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
- (LANDLOCK_MAX_NUM_LAYERS - 1);
- const bool layer_has_quiet =
- !!(quiet_optional_accesses & BIT(access_index));
-
- if (layer > youngest_layer) {
- youngest_layer = layer;
- missing = BIT(access_bit);
- should_quiet = layer_has_quiet;
- } else if (layer == youngest_layer) {
- missing |= BIT(access_bit);
- /*
- * Whether the layer has rules with quiet flag
- * covering the file accessed does not depend on
- * the access, and so the following
- * WARN_ON_ONCE() should not fail.
- */
- WARN_ON_ONCE(should_quiet && !layer_has_quiet);
- should_quiet = layer_has_quiet;
- }
- }
- access_index++;
- }
-
- *access_request = missing;
- *quiet = should_quiet;
- return youngest_layer;
-}
-
-#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
-
-static void test_get_layer_from_deny_masks(struct kunit *const test)
-{
- deny_masks_t deny_mask;
- access_mask_t access;
- optional_access_t quiet_optional_accesses;
- bool quiet;
-
- /* truncate:0 ioctl_dev:2 */
- deny_mask = 0x20;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* layer denying truncate: quiet, ioctl: not quiet */
- quiet_optional_accesses = 0b01;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* Reverse order - truncate:2 ioctl_dev:0 */
- deny_mask = 0x02;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* layer denying truncate: quiet, ioctl: not quiet */
- quiet_optional_accesses = 0b01;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- /* layer denying truncate: not quiet, ioctl: quiet */
- quiet_optional_accesses = 0b10;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* truncate:15 ioctl_dev:15 */
- deny_mask = 0xff;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access,
- LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* Both quiet (same layer so quietness must be the same) */
- quiet_optional_accesses = 0b11;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access,
- LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, true);
-}
-
-#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
-
-static bool is_valid_request(const struct landlock_request *const request)
-{
- if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
- return false;
-
- if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
- return false;
-
- if (request->access) {
- if (WARN_ON_ONCE(!(!!request->layer_masks ^
- !!request->all_existing_optional_access)))
- return false;
- } else {
- if (WARN_ON_ONCE(request->layer_masks ||
- request->all_existing_optional_access))
- return false;
- }
-
- if (request->deny_masks) {
- if (WARN_ON_ONCE(!request->all_existing_optional_access))
- return false;
- static_assert(sizeof(request->all_existing_optional_access) ==
- sizeof(u32));
- if (WARN_ON_ONCE(
- request->quiet_optional_accesses >=
- BIT(hweight32(
- request->all_existing_optional_access))))
- return false;
- }
-
- return true;
-}
-
static access_mask_t
pick_access_mask_for_request_type(const enum landlock_request_type type,
const struct access_masks access_masks)
@@ -541,62 +153,27 @@ pick_access_mask_for_request_type(const enum landlock_request_type type,
}
/**
- * landlock_log_denial - Create audit records related to a denial
+ * landlock_audit_denial - Create an audit record for a denied access request
*
* @subject: The Landlock subject's credential denying an action.
* @request: Detail of the user space request.
+ * @youngest_denied: The youngest hierarchy node that denied the access.
+ * @youngest_layer: The layer index of @youngest_denied.
+ * @missing: The set of denied access rights.
+ * @object_quiet_flag: Whether the object denied by @youngest_denied is
+ * covered by a quiet rule in that layer.
+ *
+ * Called from landlock_log_denial() with the same arguments.
*/
-void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+void landlock_audit_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const size_t youngest_layer,
+ const access_mask_t missing,
+ const bool object_quiet_flag)
{
struct audit_buffer *ab;
- struct landlock_hierarchy *youngest_denied;
- size_t youngest_layer;
- access_mask_t missing;
- bool object_quiet_flag = false, quiet_applicable_to_access = false;
-
- if (WARN_ON_ONCE(!subject || !subject->domain ||
- !subject->domain->hierarchy || !request))
- return;
-
- if (!is_valid_request(request))
- return;
-
- missing = request->access;
- if (missing) {
- /* Gets the nearest domain that denies the request. */
- if (request->layer_masks) {
- youngest_layer = get_denied_layer(subject->domain,
- &missing,
- request->layer_masks);
- object_quiet_flag =
- request->layer_masks->layers[youngest_layer]
- .quiet;
- } else {
- youngest_layer = get_layer_from_deny_masks(
- &missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
- request->deny_masks,
- request->quiet_optional_accesses,
- &object_quiet_flag);
- }
- youngest_denied =
- get_hierarchy(subject->domain, youngest_layer);
- } else {
- youngest_layer = request->layer_plus_one - 1;
- youngest_denied =
- get_hierarchy(subject->domain, youngest_layer);
- }
-
- if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
- return;
-
- /*
- * Consistently keeps track of the number of denied access requests
- * even if audit is currently disabled, or if audit rules currently
- * exclude this record type, or if landlock_restrict_self(2)'s flags
- * quiet logs.
- */
- atomic64_inc(&youngest_denied->num_denials);
+ bool quiet_applicable_to_access = false;
if (!audit_enabled)
return;
@@ -675,23 +252,19 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
}
/**
- * landlock_log_drop_domain - Create an audit record on domain deallocation
+ * landlock_audit_free_domain - Create an audit record on domain deallocation
*
* @hierarchy: The domain's hierarchy being deallocated.
*
* Only domains which previously appeared in the audit logs are logged again.
* This is useful to know when a domain will never show again in the audit log.
*
- * Called in a work queue scheduled by landlock_put_domain_deferred() called by
- * hook_cred_free().
+ * Called from landlock_log_free_domain().
*/
-void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
{
struct audit_buffer *ab;
- if (WARN_ON_ONCE(!hierarchy))
- return;
-
if (!audit_enabled)
return;
@@ -712,23 +285,3 @@ void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
hierarchy->id, atomic64_read(&hierarchy->num_denials));
audit_log_end(ab);
}
-
-#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
-
-static struct kunit_case test_cases[] = {
- /* clang-format off */
- KUNIT_CASE(test_get_hierarchy),
- KUNIT_CASE(test_get_denied_layer),
- KUNIT_CASE(test_get_layer_from_deny_masks),
- {}
- /* clang-format on */
-};
-
-static struct kunit_suite test_suite = {
- .name = "landlock_audit",
- .test_cases = test_cases,
-};
-
-kunit_test_suite(test_suite);
-
-#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 4617a855712c4..1b36bb3c6cfdd 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -8,68 +8,39 @@
#ifndef _SECURITY_LANDLOCK_AUDIT_H
#define _SECURITY_LANDLOCK_AUDIT_H
-#include <linux/audit.h>
-#include <linux/lsm_audit.h>
+#include <linux/types.h>
#include "access.h"
struct landlock_cred_security;
struct landlock_hierarchy;
-
-enum landlock_request_type {
- LANDLOCK_REQUEST_PTRACE = 1,
- LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
- LANDLOCK_REQUEST_FS_ACCESS,
- LANDLOCK_REQUEST_NET_ACCESS,
- LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
- LANDLOCK_REQUEST_SCOPE_SIGNAL,
-};
-
-/*
- * We should be careful to only use a variable of this type for
- * landlock_log_denial(). This way, the compiler can remove it entirely if
- * CONFIG_AUDIT is not set.
- */
-struct landlock_request {
- /* Mandatory fields. */
- enum landlock_request_type type;
- struct common_audit_data audit;
-
- /**
- * layer_plus_one: First layer level that denies the request + 1. The
- * extra one is useful to detect uninitialized field.
- */
- size_t layer_plus_one;
-
- /* Required field for configurable access control. */
- access_mask_t access;
-
- /* Required fields for requests with layer masks. */
- const struct layer_masks *layer_masks;
-
- /* Required fields for requests with deny masks. */
- const access_mask_t all_existing_optional_access;
- deny_masks_t deny_masks;
- optional_access_t quiet_optional_accesses;
-};
+struct landlock_request;
#ifdef CONFIG_AUDIT
-void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy);
+void landlock_audit_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const size_t youngest_layer,
+ const access_mask_t missing,
+ const bool object_quiet_flag);
-void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request);
+void landlock_audit_free_domain(
+ const struct landlock_hierarchy *const hierarchy);
#else /* CONFIG_AUDIT */
static inline void
-landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+landlock_audit_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const size_t youngest_layer, const access_mask_t missing,
+ const bool object_quiet_flag)
{
}
static inline void
-landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
{
}
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index 58b544993db49..03449c26247e5 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -41,7 +41,7 @@ static void hook_cred_free(struct cred *const cred)
landlock_put_domain_deferred(dom);
}
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
{
@@ -50,16 +50,16 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
return 0;
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
LSM_HOOK_INIT(cred_free, hook_cred_free),
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
};
__init void landlock_add_cred_hooks(void)
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index e7830cfed0416..a5ff9957949a1 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -36,7 +36,7 @@ struct landlock_cred_security {
*/
struct landlock_domain *domain;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @domain_exec: Bitmask identifying the domain layers that were enforced by
* the current task's executed file (i.e. no new execve(2) since
@@ -50,17 +50,17 @@ struct landlock_cred_security {
* not require a current domain.
*/
u8 log_subdomains_off : 1;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
} __packed;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/* Makes sure all layer executions can be stored. */
static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security,
domain_exec)) >=
LANDLOCK_MAX_NUM_LAYERS);
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline struct landlock_cred_security *
landlock_cred(const struct cred *cred)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 2fd8b8532c8a8..70060e5cf6713 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -172,11 +172,11 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
/* Clear the bits where the layer in the rule grants access. */
masks->layers[layer->level - 1].access &= ~layer->access;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/* Collect rule flags for each layer. */
if (layer->flags.quiet)
masks->layers[layer->level - 1].quiet = true;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
@@ -239,16 +239,16 @@ landlock_init_layer_masks(const struct landlock_domain *const domain,
masks->layers[i].access = access_request & handled;
handled_accesses |= masks->layers[i].access;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
i++) {
masks->layers[i].access = 0;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
return handled_accesses;
@@ -465,14 +465,14 @@ landlock_merge_ruleset(struct landlock_domain *const parent,
if (err)
return ERR_PTR(err);
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return no_free_ptr(new_dom);
}
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* get_current_exe - Get the current's executable path, if any
@@ -754,4 +754,4 @@ kunit_test_suite(test_suite);
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 7cd6154dd4e4c..7a77410727e2a 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -22,7 +22,7 @@
#include <linux/workqueue.h>
#include "access.h"
-#include "audit.h"
+#include "log.h"
#include "ruleset.h"
enum landlock_log_status {
@@ -85,7 +85,7 @@ struct landlock_hierarchy {
*/
refcount_t usage;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @log_status: Whether this domain should be logged or not. Because
* concurrent log entries may be created at the same time, it is still
@@ -120,10 +120,10 @@ struct landlock_hierarchy {
* logged) if the related object is marked as quiet.
*/
struct access_masks quiet_masks;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
};
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
deny_masks_t
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
@@ -146,7 +146,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
kfree(hierarchy->details);
}
-#else /* CONFIG_AUDIT */
+#else /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline int
landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
@@ -159,7 +159,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
{
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline void
landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -173,7 +173,7 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
const struct landlock_hierarchy *const freeme = hierarchy;
- landlock_log_drop_domain(hierarchy);
+ landlock_log_free_domain(hierarchy);
landlock_free_hierarchy_details(hierarchy);
hierarchy = hierarchy->parent;
kfree(freeme);
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 0c98862ced424..9abffda2bc98f 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -43,12 +43,12 @@
#include <uapi/linux/landlock.h>
#include "access.h"
-#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "fs.h"
#include "limits.h"
+#include "log.h"
#include "object.h"
#include "ruleset.h"
#include "setup.h"
@@ -933,10 +933,11 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
path_put(&walker_path);
/*
- * Check CONFIG_AUDIT to enable elision of log_request_parent* and
- * associated caller's stack variables thanks to dead code elimination.
+ * Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of
+ * log_request_parent* and associated caller's stack variables thanks to
+ * dead code elimination.
*/
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
if (!allowed_parent1 && log_request_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
@@ -952,7 +953,7 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
log_request_parent2->access = access_masked_parent2;
log_request_parent2->layer_masks = layer_masks_parent2;
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return allowed_parent1 && allowed_parent2;
}
@@ -1830,14 +1831,14 @@ static int hook_file_open(struct file *const file)
* file access rights in the opened struct file.
*/
landlock_file(file)->allowed_access = allowed_access;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
landlock_file(file)->deny_masks = landlock_get_deny_masks(
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
landlock_file(file)->quiet_optional_accesses =
landlock_get_quiet_optional_accesses(
_LANDLOCK_ACCESS_FS_OPTIONAL,
landlock_file(file)->deny_masks, &layer_masks);
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
if (access_mask_subset(open_access_request, allowed_access))
return 0;
@@ -1871,10 +1872,10 @@ static int hook_file_truncate(struct file *const file)
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.deny_masks = landlock_file(file)->deny_masks,
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
});
return -EACCES;
}
@@ -1911,10 +1912,10 @@ static int hook_file_ioctl_common(const struct file *const file,
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.deny_masks = landlock_file(file)->deny_masks,
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
});
return -EACCES;
}
@@ -1990,9 +1991,9 @@ static void hook_file_set_fowner(struct file *file)
prev_tg = landlock_file(file)->fown_tg;
landlock_file(file)->fown_subject = fown_subject;
landlock_file(file)->fown_tg = fown_tg;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
landlock_file(file)->fown_layer = fown_layer;
-#endif /* CONFIG_AUDIT*/
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/* May be called in an RCU read-side critical section. */
landlock_put_domain_deferred(prev_dom);
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index b4421d9df68ff..c16f24e30bd5c 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -57,7 +57,7 @@ struct landlock_file_security {
*/
access_mask_t allowed_access;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @deny_masks: Domain layer levels that deny an optional access (see
* _LANDLOCK_ACCESS_FS_OPTIONAL).
@@ -75,7 +75,7 @@ struct landlock_file_security {
* LANDLOCK_SCOPE_SIGNAL.
*/
u8 fown_layer;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/**
* @fown_subject: Landlock credential of the task that set the PID that
@@ -97,7 +97,7 @@ struct landlock_file_security {
struct pid *fown_tg;
};
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/* Makes sure all layers can be identified. */
/* clang-format off */
@@ -113,7 +113,7 @@ static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security,
quiet_optional_accesses)) >=
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/**
* struct landlock_superblock_security - Superblock security blob
diff --git a/security/landlock/id.h b/security/landlock/id.h
index 45dcfb9e9a8b7..2a43c2b523a8e 100644
--- a/security/landlock/id.h
+++ b/security/landlock/id.h
@@ -8,18 +8,18 @@
#ifndef _SECURITY_LANDLOCK_ID_H
#define _SECURITY_LANDLOCK_ID_H
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
void __init landlock_init_id(void);
u64 landlock_get_id_range(size_t number_of_ids);
-#else /* CONFIG_AUDIT */
+#else /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline void __init landlock_init_id(void)
{
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
#endif /* _SECURITY_LANDLOCK_ID_H */
diff --git a/security/landlock/log.c b/security/landlock/log.c
new file mode 100644
index 0000000000000..6dd3373c4ba4a
--- /dev/null
+++ b/security/landlock/log.c
@@ -0,0 +1,502 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Log helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/bitops.h>
+#include <uapi/linux/landlock.h>
+
+#include "access.h"
+#include "audit.h"
+#include "common.h"
+#include "cred.h"
+#include "domain.h"
+#include "limits.h"
+#include "log.h"
+#include "ruleset.h"
+
+static struct landlock_hierarchy *
+get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
+{
+ struct landlock_hierarchy *hierarchy = domain->hierarchy;
+ ssize_t i;
+
+ if (WARN_ON_ONCE(layer >= domain->num_layers))
+ return hierarchy;
+
+ for (i = domain->num_layers - 1; i > layer; i--) {
+ if (WARN_ON_ONCE(!hierarchy->parent))
+ break;
+
+ hierarchy = hierarchy->parent;
+ }
+
+ return hierarchy;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_hierarchy(struct kunit *const test)
+{
+ struct landlock_hierarchy dom0_hierarchy = {
+ .id = 10,
+ };
+ struct landlock_hierarchy dom1_hierarchy = {
+ .parent = &dom0_hierarchy,
+ .id = 20,
+ };
+ struct landlock_hierarchy dom2_hierarchy = {
+ .parent = &dom1_hierarchy,
+ .id = 30,
+ };
+ struct landlock_domain dom2 = {
+ .hierarchy = &dom2_hierarchy,
+ .num_layers = 3,
+ };
+
+ KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
+ KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
+ KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
+ /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+/* Get the youngest layer that denied the access_request. */
+static size_t get_denied_layer(const struct landlock_domain *const domain,
+ access_mask_t *const access_request,
+ const struct layer_masks *masks)
+{
+ for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
+ if (masks->layers[i].access & *access_request) {
+ *access_request &= masks->layers[i].access;
+ return i;
+ }
+ }
+
+ /* Not found - fall back to default values */
+ *access_request = 0;
+ return domain->num_layers - 1;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_denied_layer(struct kunit *const test)
+{
+ const struct landlock_domain dom = {
+ .num_layers = 5,
+ };
+ const struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
+ };
+ access_mask_t access;
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE;
+ KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE;
+ KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
+
+ access = LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_WRITE_FILE;
+ KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
+ KUNIT_EXPECT_EQ(test, access, 0);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static size_t
+get_layer_from_deny_masks(access_mask_t *const access_request,
+ const access_mask_t all_existing_optional_access,
+ const deny_masks_t deny_masks,
+ optional_access_t quiet_optional_accesses,
+ bool *quiet)
+{
+ const unsigned long access_opt = all_existing_optional_access;
+ const unsigned long access_req = *access_request;
+ access_mask_t missing = 0;
+ size_t youngest_layer = 0;
+ size_t access_index = 0;
+ unsigned long access_bit;
+ bool should_quiet = false;
+
+ /* This will require change with new object types. */
+ WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ for_each_set_bit(access_bit, &access_opt,
+ BITS_PER_TYPE(access_mask_t)) {
+ if (access_req & BIT(access_bit)) {
+ const size_t layer =
+ (deny_masks >>
+ (access_index *
+ HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
+ (LANDLOCK_MAX_NUM_LAYERS - 1);
+ const bool layer_has_quiet =
+ !!(quiet_optional_accesses & BIT(access_index));
+
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ should_quiet = layer_has_quiet;
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ /*
+ * Whether the layer has rules with quiet flag
+ * covering the file accessed does not depend on
+ * the access, and so the following
+ * WARN_ON_ONCE() should not fail.
+ */
+ WARN_ON_ONCE(should_quiet && !layer_has_quiet);
+ should_quiet = layer_has_quiet;
+ }
+ }
+ access_index++;
+ }
+
+ *access_request = missing;
+ *quiet = should_quiet;
+ return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_from_deny_masks(struct kunit *const test)
+{
+ deny_masks_t deny_mask;
+ access_mask_t access;
+ optional_access_t quiet_optional_accesses;
+ bool quiet;
+
+ /* truncate:0 ioctl_dev:2 */
+ deny_mask = 0x20;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Reverse order - truncate:2 ioctl_dev:0 */
+ deny_mask = 0x02;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ /* layer denying truncate: not quiet, ioctl: quiet */
+ quiet_optional_accesses = 0b10;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* truncate:15 ioctl_dev:15 */
+ deny_mask = 0xff;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Both quiet (same layer so quietness must be the same) */
+ quiet_optional_accesses = 0b11;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static bool is_valid_request(const struct landlock_request *const request)
+{
+ if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
+ return false;
+
+ if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
+ return false;
+
+ if (request->access) {
+ if (WARN_ON_ONCE(!(!!request->layer_masks ^
+ !!request->all_existing_optional_access)))
+ return false;
+ } else {
+ if (WARN_ON_ONCE(request->layer_masks ||
+ request->all_existing_optional_access))
+ return false;
+ }
+
+ if (request->deny_masks) {
+ if (WARN_ON_ONCE(!request->all_existing_optional_access))
+ return false;
+ static_assert(sizeof(request->all_existing_optional_access) ==
+ sizeof(u32));
+ if (WARN_ON_ONCE(
+ request->quiet_optional_accesses >=
+ BIT(hweight32(
+ request->all_existing_optional_access))))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * landlock_log_denial - Log a denied access
+ *
+ * @subject: The Landlock subject's credential denying an action.
+ * @request: Detail of the user space request.
+ */
+void landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request)
+{
+ struct landlock_hierarchy *youngest_denied;
+ size_t youngest_layer;
+ access_mask_t missing;
+ bool object_quiet_flag = false;
+
+ if (WARN_ON_ONCE(!subject || !subject->domain ||
+ !subject->domain->hierarchy || !request))
+ return;
+
+ if (!is_valid_request(request))
+ return;
+
+ missing = request->access;
+ if (missing) {
+ /* Gets the nearest domain that denies the request. */
+ if (request->layer_masks) {
+ youngest_layer = get_denied_layer(subject->domain,
+ &missing,
+ request->layer_masks);
+ object_quiet_flag =
+ request->layer_masks->layers[youngest_layer]
+ .quiet;
+ } else {
+ youngest_layer = get_layer_from_deny_masks(
+ &missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ request->deny_masks,
+ request->quiet_optional_accesses,
+ &object_quiet_flag);
+ }
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ } else {
+ youngest_layer = request->layer_plus_one - 1;
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ }
+
+ if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
+ return;
+
+ /*
+ * Consistently keeps track of the number of denied access requests even
+ * if audit is currently disabled, or if audit rules currently exclude
+ * this record type, or if landlock_restrict_self(2)'s flags quiet logs.
+ */
+ atomic64_inc(&youngest_denied->num_denials);
+
+ landlock_audit_denial(subject, request, youngest_denied, youngest_layer,
+ missing, object_quiet_flag);
+}
+
+/**
+ * landlock_log_free_domain - Log domain deallocation
+ *
+ * @hierarchy: The domain's hierarchy being deallocated.
+ *
+ * Called in a work queue scheduled by landlock_put_domain_deferred() called by
+ * hook_cred_free().
+ */
+void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
+{
+ if (WARN_ON_ONCE(!hierarchy))
+ return;
+
+ landlock_audit_free_domain(hierarchy);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_get_hierarchy),
+ KUNIT_CASE(test_get_denied_layer),
+ KUNIT_CASE(test_get_layer_from_deny_masks),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_log",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/log.h b/security/landlock/log.h
new file mode 100644
index 0000000000000..25afc17cf0557
--- /dev/null
+++ b/security/landlock/log.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Log helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_LOG_H
+#define _SECURITY_LANDLOCK_LOG_H
+
+#include <linux/lsm_audit.h>
+
+#include "access.h"
+
+struct landlock_cred_security;
+struct landlock_hierarchy;
+
+enum landlock_request_type {
+ LANDLOCK_REQUEST_PTRACE = 1,
+ LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
+ LANDLOCK_REQUEST_FS_ACCESS,
+ LANDLOCK_REQUEST_NET_ACCESS,
+ LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ LANDLOCK_REQUEST_SCOPE_SIGNAL,
+};
+
+/*
+ * We should be careful to only use a variable of this type for
+ * landlock_log_denial(). This way, the compiler can remove it entirely if
+ * CONFIG_SECURITY_LANDLOCK_LOG is not set.
+ */
+struct landlock_request {
+ /* Mandatory fields. */
+ enum landlock_request_type type;
+ struct common_audit_data audit;
+
+ /**
+ * layer_plus_one: First layer level that denies the request + 1. The
+ * extra one is useful to detect uninitialized field.
+ */
+ size_t layer_plus_one;
+
+ /* Required field for configurable access control. */
+ access_mask_t access;
+
+ /* Required fields for requests with layer masks. */
+ const struct layer_masks *layer_masks;
+
+ /* Required fields for requests with deny masks. */
+ const access_mask_t all_existing_optional_access;
+ deny_masks_t deny_masks;
+ optional_access_t quiet_optional_accesses;
+};
+
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+
+void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy);
+
+void landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request);
+
+#else /* CONFIG_SECURITY_LANDLOCK_LOG */
+
+static inline void
+landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
+{
+}
+
+static inline void
+landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request)
+{
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+
+#endif /* _SECURITY_LANDLOCK_LOG_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 7447738ca2e6e..e27b3ba156645 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -12,11 +12,11 @@
#include <linux/socket.h>
#include <net/ipv6.h>
-#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
+#include "log.h"
#include "net.h"
#include "ruleset.h"
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index fe8505dc0ba52..0ad20a6f9564f 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -574,11 +574,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
new_llcred = landlock_cred(new_cred);
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
prev_log_subdomains = !new_llcred->log_subdomains_off;
new_llcred->log_subdomains_off = !prev_log_subdomains ||
!log_subdomains;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/*
* The only case when a ruleset may not be set is if
@@ -600,20 +600,20 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
return PTR_ERR(new_dom);
}
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_dom->hierarchy->log_same_exec = log_same_exec;
new_dom->hierarchy->log_new_exec = log_new_exec;
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/* Replaces the old (prepared) domain. */
landlock_put_domain(new_llcred->domain);
new_llcred->domain = new_dom;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 40e6bfa4bc759..c0da22736ea0b 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -20,11 +20,11 @@
#include <net/af_unix.h>
#include <net/sock.h>
-#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "fs.h"
+#include "log.h"
#include "ruleset.h"
#include "setup.h"
#include "task.h"
@@ -435,9 +435,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
.type = LSM_AUDIT_DATA_TASK,
.u.tsk = tsk,
},
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
});
return -EPERM;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y 5/5] landlock: Work around gcc-16 -Wuninitialized warning
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
` (2 preceding siblings ...)
2026-09-29 13:46 ` [PATCH 7.2.y 4/5] landlock: Split denial logging from audit into common framework Sasha Levin
@ 2026-09-29 13:46 ` Sasha Levin
2026-09-29 18:29 ` [PATCH 7.2.y v2] " Mickaël Salaün
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-09-29 13:46 UTC (permalink / raw)
To: stable; +Cc: Arnd Bergmann, Mickaël Salaün, Sasha Levin
From: Arnd Bergmann <arnd@arndb.de>
[ Upstream commit c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d ]
gcc has a bug with -ftrivial-auto-var-init=pattern that produces a
warning for correct code that uses sparse bitfields:
security/landlock/fs.c: In function 'is_access_to_paths_allowed.isra':
security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized]
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c:767:28: note: '_layer_masks_child1' declared here
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c: In function 'hook_unix_find':
security/landlock/fs.c:1649:28: error: 'layer_masks' is used uninitialized [-Werror=uninitialized]
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
security/landlock/fs.c:1649:28: note: 'layer_masks' declared here
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
To work around this, change the definition of struct layer_mask to
use an explictit padding field.
Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743
Link: https://lore.kernel.org/all/20260619082133.3504146-1-arnd@kernel.org/
Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Link: https://patch.msgid.link/20260915201036.3527935-1-arnd@kernel.org
[mic: Use BITS_PER_TYPE(), fix kdoc warnings, fix commit message
according to v2 changes]
Cc: stable@vger.kernel.org
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/landlock/access.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index bbbb41f41147c..f843835851d01 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -61,6 +61,10 @@ union access_masks_all {
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
+#define _LANDLOCK_LAYER_MASK_PADDING \
+ (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \
+ IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG))
+
/**
* struct layer_mask - The access rights and rule flags for a layer.
*
@@ -81,6 +85,10 @@ struct layer_mask {
*/
access_mask_t quiet : 1;
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ /**
+ * @__pad: Padding for the compiler's bitfield initialization.
+ */
+ access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING;
} __packed __aligned(sizeof(access_mask_t));
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 7.2.y v2] landlock: Work around gcc-16 -Wuninitialized warning
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
` (3 preceding siblings ...)
2026-09-29 13:46 ` [PATCH 7.2.y 5/5] landlock: Work around gcc-16 -Wuninitialized warning Sasha Levin
@ 2026-09-29 18:29 ` Mickaël Salaün
4 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-09-29 18:29 UTC (permalink / raw)
To: stable
Cc: Mickaël Salaün, Greg Kroah-Hartman, Sasha Levin,
Günther Noack, Tingmao Wang, Arnd Bergmann
From: Arnd Bergmann <arnd@arndb.de>
[ Upstream commit c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d ]
gcc has a bug with -ftrivial-auto-var-init=pattern that produces a
warning for correct code that uses sparse bitfields:
security/landlock/fs.c: In function 'is_access_to_paths_allowed.isra':
security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized]
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c:767:28: note: '_layer_masks_child1' declared here
767 | struct layer_masks _layer_masks_child1, _layer_masks_child2;
| ^~~~~~~~~~~~~~~~~~~
security/landlock/fs.c: In function 'hook_unix_find':
security/landlock/fs.c:1649:28: error: 'layer_masks' is used uninitialized [-Werror=uninitialized]
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
security/landlock/fs.c:1649:28: note: 'layer_masks' declared here
1649 | struct layer_masks layer_masks;
| ^~~~~~~~~~~
To work around this, change the definition of struct layer_mask to
use an explictit padding field.
Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743
Link: https://lore.kernel.org/all/20260619082133.3504146-1-arnd@kernel.org/
Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Link: https://patch.msgid.link/20260915201036.3527935-1-arnd@kernel.org
[mic: Use BITS_PER_TYPE(), fix kdoc warnings, fix commit message
according to v2 changes]
Cc: stable@vger.kernel.org
Signed-off-by: Mickaël Salaün <mic@digikod.net>
[mic: Backport: use CONFIG_AUDIT to calculate the padding width]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
Changes since v1:
https://patch.msgid.link/20260929134605.3516662-5-sashal@kernel.org
- Drop the four context-only prerequisite backports and use CONFIG_AUDIT
to calculate the target's padding width.
---
security/landlock/access.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5..a0fa1009ccdb 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -61,6 +61,10 @@ union access_masks_all {
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
+#define _LANDLOCK_LAYER_MASK_PADDING \
+ (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \
+ IS_ENABLED(CONFIG_AUDIT))
+
/**
* struct layer_mask - The access rights and rule flags for a layer.
*
@@ -81,6 +85,10 @@ struct layer_mask {
*/
access_mask_t quiet : 1;
#endif /* CONFIG_AUDIT */
+ /**
+ * @__pad: Padding for the compiler's bitfield initialization.
+ */
+ access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING;
} __packed __aligned(sizeof(access_mask_t));
/*
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-29 18:29 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-29 6:48 FAILED: patch "[PATCH] landlock: Work around gcc-16 -Wuninitialized warning" failed to apply to 7.2-stable tree gregkh
2026-09-29 13:46 ` [PATCH 7.2.y 1/5] landlock: Prepare ruleset and domain type split Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 2/5] landlock: Move domain query functions to domain.c Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 3/5] landlock: Split struct landlock_domain from struct landlock_ruleset Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 4/5] landlock: Split denial logging from audit into common framework Sasha Levin
2026-09-29 13:46 ` [PATCH 7.2.y 5/5] landlock: Work around gcc-16 -Wuninitialized warning Sasha Levin
2026-09-29 18:29 ` [PATCH 7.2.y v2] " Mickaël Salaün
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox