[PATCH v5 01/10] landlock: Add a place for flags to layer rules
Tingmao Wang
m at maowtm.org
Sun Nov 23 20:57:37 UTC 2025
To avoid unnecessarily increasing the size of struct landlock_layer, we
make the layer level a u8 and use the space to store the flags struct.
Cc: Justin Suess <utilityemal77 at gmail.com>
Signed-off-by: Tingmao Wang <m at maowtm.org>
---
Changes in v5:
- Move rule_flags into landlock_request. This lets us get rid of the
extra parameters to is_access_to_paths_allowed (and later on,
landlock_log_denial), and thus less code changes.
Changes in v3:
- Comment changes, move local variables, simplify if branch
Changes in v2:
- Comment changes
- Rebased to include disconnected directory handling changes on mic/next
and add backing up of collected_rule_flags.
security/landlock/audit.h | 2 ++
security/landlock/fs.c | 71 +++++++++++++++++++++++++++----------
security/landlock/net.c | 3 +-
security/landlock/ruleset.c | 7 +++-
security/landlock/ruleset.h | 26 ++++++++++++--
5 files changed, 86 insertions(+), 23 deletions(-)
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 92428b7fc4d8..d66c6d936438 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -49,6 +49,8 @@ struct landlock_request {
/* Required fields for requests with deny masks. */
const access_mask_t all_existing_optional_access;
deny_masks_t deny_masks;
+
+ struct collected_rule_flags rule_flags;
};
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index b33d8ac239c1..58dba2f7c6cd 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -775,6 +775,9 @@ static bool is_access_to_paths_allowed(
_layer_masks_parent2_bkp[LANDLOCK_NUM_ACCESS_FS];
layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL,
(*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL;
+ struct collected_rule_flags *rule_flags_parent1 = &log_request_parent1->rule_flags;
+ struct collected_rule_flags *rule_flags_parent2 = &log_request_parent2->rule_flags;
+ struct collected_rule_flags _rule_flag_parent1_bkp, _rule_flag_parent2_bkp;
if (!access_request_parent1 && !access_request_parent2)
return true;
@@ -800,6 +803,8 @@ static bool is_access_to_paths_allowed(
*/
memcpy(&_layer_masks_parent2_bkp, layer_masks_parent2,
sizeof(_layer_masks_parent2_bkp));
+ memcpy(&_rule_flag_parent2_bkp, rule_flags_parent2,
+ sizeof(_rule_flag_parent2_bkp));
allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
/*
@@ -826,26 +831,38 @@ static bool is_access_to_paths_allowed(
*/
memcpy(&_layer_masks_parent1_bkp, layer_masks_parent1,
sizeof(_layer_masks_parent1_bkp));
+ memcpy(&_rule_flag_parent1_bkp, rule_flags_parent1,
+ sizeof(_rule_flag_parent1_bkp));
allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
is_dom_check_bkp = is_dom_check;
if (unlikely(dentry_child1)) {
+ /*
+ * Get the layer masks for the child dentries for use by domain
+ * check later. The rule_flags for child1 should have been
+ * included in rule_flags_parent1 already (cf.
+ * collect_domain_accesses), and is not relevant for domain check,
+ * so we don't have to pass it to landlock_unmask_layers.
+ */
landlock_unmask_layers(
find_rule(domain, dentry_child1),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child1, LANDLOCK_KEY_INODE),
- &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
+ &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1),
+ NULL);
layer_masks_child1 = &_layer_masks_child1;
child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
+ /* See above comment for why NULL is passed as rule_flags_masks. */
landlock_unmask_layers(
find_rule(domain, dentry_child2),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child2, LANDLOCK_KEY_INODE),
- &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
+ &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2),
+ NULL);
layer_masks_child2 = &_layer_masks_child2;
child2_is_directory = d_is_dir(dentry_child2);
}
@@ -901,16 +918,18 @@ static bool is_access_to_paths_allowed(
NULL :
find_rule(domain, walker_path.dentry);
- allowed_parent1 = allowed_parent1 ||
- landlock_unmask_layers(
- rule, access_masked_parent1,
- layer_masks_parent1,
- ARRAY_SIZE(*layer_masks_parent1));
- allowed_parent2 = allowed_parent2 ||
- landlock_unmask_layers(
- rule, access_masked_parent2,
- layer_masks_parent2,
- ARRAY_SIZE(*layer_masks_parent2));
+ allowed_parent1 =
+ allowed_parent1 ||
+ landlock_unmask_layers(rule, access_masked_parent1,
+ layer_masks_parent1,
+ ARRAY_SIZE(*layer_masks_parent1),
+ rule_flags_parent1);
+ allowed_parent2 =
+ allowed_parent2 ||
+ landlock_unmask_layers(rule, access_masked_parent2,
+ layer_masks_parent2,
+ ARRAY_SIZE(*layer_masks_parent2),
+ rule_flags_parent2);
/* Stops when a rule from each layer grants access. */
if (allowed_parent1 && allowed_parent2) {
@@ -947,10 +966,16 @@ static bool is_access_to_paths_allowed(
memcpy(&_layer_masks_parent1_bkp,
layer_masks_parent1,
sizeof(_layer_masks_parent1_bkp));
+ memcpy(&_rule_flag_parent1_bkp,
+ rule_flags_parent1,
+ sizeof(_rule_flag_parent1_bkp));
if (layer_masks_parent2) {
memcpy(&_layer_masks_parent2_bkp,
layer_masks_parent2,
sizeof(_layer_masks_parent2_bkp));
+ memcpy(&_rule_flag_parent2_bkp,
+ rule_flags_parent2,
+ sizeof(_rule_flag_parent2_bkp));
is_dom_check_bkp = is_dom_check;
}
@@ -999,11 +1024,15 @@ static bool is_access_to_paths_allowed(
*/
memcpy(layer_masks_parent1, &_layer_masks_parent1_bkp,
sizeof(_layer_masks_parent1_bkp));
+ memcpy(rule_flags_parent1, &_rule_flag_parent1_bkp,
+ sizeof(_rule_flag_parent1_bkp));
allowed_parent1 =
is_layer_masks_allowed(&_layer_masks_parent1_bkp);
if (layer_masks_parent2) {
memcpy(layer_masks_parent2, &_layer_masks_parent2_bkp,
sizeof(_layer_masks_parent2_bkp));
+ memcpy(rule_flags_parent2, &_rule_flag_parent2_bkp,
+ sizeof(_rule_flag_parent2_bkp));
allowed_parent2 = is_layer_masks_allowed(
&_layer_masks_parent2_bkp);
@@ -1139,7 +1168,8 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
static bool collect_domain_accesses(
const struct landlock_ruleset *const domain,
const struct path *const mnt_dir, struct dentry *dir,
- layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS])
+ layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS],
+ struct collected_rule_flags *const rule_flags)
{
access_mask_t access_dom;
bool ret = false;
@@ -1158,9 +1188,9 @@ static bool collect_domain_accesses(
struct dentry *parent_dentry;
/* Gets all layers allowing all domain accesses. */
- if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
- layer_masks_dom,
- ARRAY_SIZE(*layer_masks_dom))) {
+ if (landlock_unmask_layers(
+ find_rule(domain, dir), access_dom, layer_masks_dom,
+ ARRAY_SIZE(*layer_masks_dom), rule_flags)) {
/*
* Before allowing this side of the access request, checks that the
* walk was not in a disconnected directory.
@@ -1327,11 +1357,14 @@ static int current_check_refer_path(struct dentry *const old_dentry,
old_dentry->d_parent;
/* new_dir->dentry is equal to new_dentry->d_parent */
- allow_parent1 = collect_domain_accesses(
- subject->domain, &mnt_dir, old_parent, &layer_masks_parent1);
+ allow_parent1 = collect_domain_accesses(subject->domain, &mnt_dir,
+ old_parent,
+ &layer_masks_parent1,
+ &request1.rule_flags);
allow_parent2 = collect_domain_accesses(subject->domain, &mnt_dir,
new_dir->dentry,
- &layer_masks_parent2);
+ &layer_masks_parent2,
+ &request2.rule_flags);
if (allow_parent1 && allow_parent2)
return 0;
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 1f3915a90a80..fc6369dffa51 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -48,6 +48,7 @@ static int current_check_access_socket(struct socket *const sock,
{
__be16 port;
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+ struct collected_rule_flags rule_flags = {};
const struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
@@ -179,7 +180,7 @@ static int current_check_access_socket(struct socket *const sock,
access_request, &layer_masks,
LANDLOCK_KEY_NET_PORT);
if (landlock_unmask_layers(rule, access_request, &layer_masks,
- ARRAY_SIZE(layer_masks)))
+ ARRAY_SIZE(layer_masks), &rule_flags))
return 0;
audit_net.family = address->sa_family;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index dfcdc19ea268..81cdf87d1c79 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -624,7 +624,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
- const size_t masks_array_size)
+ const size_t masks_array_size,
+ struct collected_rule_flags *const rule_flags)
{
size_t layer_level;
@@ -651,6 +652,10 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
unsigned long access_bit;
bool is_empty;
+ /* Collect rule flags for each layer. */
+ if (rule_flags && layer->flags.quiet)
+ rule_flags->quiet_masks |= layer_bit;
+
/*
* Records in @layer_masks which layer grants access to each requested
* access: bit cleared if the related layer grants access.
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 1a78cba662b2..9790c60c0c00 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -29,7 +29,18 @@ struct landlock_layer {
/**
* @level: Position of this layer in the layer stack. Starts from 1.
*/
- u16 level;
+ u8 level;
+ /**
+ * @flags: Bitfield for special flags attached to this rule.
+ */
+ struct {
+ /**
+ * @quiet: Suppresses denial audit logs for the object covered by
+ * this rule in this domain. For filesystem rules, this inherits
+ * down the file hierarchy.
+ */
+ bool quiet:1;
+ } flags;
/**
* @access: Bitfield of allowed actions on the kernel object. They are
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
@@ -37,6 +48,16 @@ struct landlock_layer {
access_mask_t access;
};
+/**
+ * struct collected_rule_flags - Hold accumulated flags for each layer.
+ */
+struct collected_rule_flags {
+ /**
+ * @quiet_masks: Layers for which the quiet flag is effective.
+ */
+ layer_mask_t quiet_masks;
+};
+
/**
* union landlock_key - Key of a ruleset's red-black tree
*/
@@ -304,7 +325,8 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
- const size_t masks_array_size);
+ const size_t masks_array_size,
+ struct collected_rule_flags *const rule_flags);
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
--
2.52.0
More information about the Linux-security-module-archive
mailing list