* [PATCH v9 6/9] landlock: Add documentation for LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
Add documentation of the flag to the userspace API, describing the
functionality of the flag and parent directory protections.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
Changes since v8:
- Expanded the userspace-api documentation: the conservative seal
(same-directory renames and hard links denied; rules keyed by inode),
the best-effort ancestor walk, guidance to discard a partially applied
policy on error, and the threat-model paragraph.
- Updated the ABI references to version 11.
- Rebased onto mic/next.
Documentation/userspace-api/landlock.rst | 44 ++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 5a63d4476c1c..01623e0ab95d 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -789,6 +789,50 @@ when at least one sys_landlock_add_rule() call is made for it with the
``LANDLOCK_ADD_RULE_QUIET`` flag, additional add-rule calls for the same
object without this flag do not clear it.
+Filesystem inheritance suppression (ABI < 11)
+---------------------------------------------
+
+Starting with the Landlock ABI version 11, it is possible to prevent a
+directory or file from inheriting its parent's access grants by using the
+``LANDLOCK_ADD_RULE_NO_INHERIT`` flag passed to sys_landlock_add_rule().
+This is useful for policies where a parent directory needs broader access
+than its children.
+
+To mitigate sandbox-restart attacks, the tagged inode and all of its
+ancestors up to the VFS root cannot be removed, renamed, reparented, or
+linked into or out of other directories.
+
+This seal is intentionally conservative: every rename, removal or link
+operation that targets a sealed inode is denied, including same-directory
+renames and hard links that do not change the inode's parent. Landlock rules
+are keyed by inode, so such operations could not by themselves bypass a seal,
+but denying them as well keeps enforcement simple and leaves no edge cases
+that could weaken the guarantee.
+
+Inheritance of access grants from descendants of an inode tagged with
+``LANDLOCK_ADD_RULE_NO_INHERIT`` is unaffected: such descendants continue
+to inherit from the tagged inode normally, unless they also carry this
+flag.
+
+Because sealing an inode also seals all of its ancestors, the kernel walks
+the path up to the VFS root while adding such a rule, sealing each ancestor in
+turn. This walk is best effort: it is not serialized against concurrent
+renames, so a rename that reparents one of the ancestors while the walk is in
+progress may leave the seal incomplete.
+
+Similarly, if sys_landlock_add_rule() returns an error while adding a
+``LANDLOCK_ADD_RULE_NO_INHERIT`` rule (for example because of memory
+pressure), the ruleset may have been left with the rule's object and only some
+of its ancestors sealed. Such a ruleset should be discarded rather than
+enforced.
+
+This is not a security concern. Changes to the filesystem hierarchy between
+the time a ruleset is built and the time it is enforced are outside of
+Landlock's threat model: a ruleset only describes the restrictions that take
+effect once it is enforced, and what happens to the hierarchy beforehand is
+not controlled by Landlock. Once enforced, the seals that were established
+deny the topology changes they cover.
+
.. _kernel_support:
Kernel support
--
2.54.0
^ permalink raw reply related
* [PATCH v9 5/9] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
Make %LANDLOCK_ADD_RULE_NO_INHERIT actually enforce its semantics:
- Tag the new rule's layer with @no_inherit and
@has_no_inherit_descendant so landlock_unmask_layers() stops walking
up the hierarchy once it has been seen, and so the rule's own object
is sealed against topology changes.
- Walk from the rule's path up to the VFS root in
landlock_append_fs_rule(), inserting a zero-access rule on each
ancestor with @has_no_inherit_descendant set, so topology changes
(rename, rmdir, link, ...) on any ancestor are denied too.
- Add deny_no_inherit_topology_change(), called from
current_check_refer_path(), hook_path_unlink() and hook_path_rmdir()
to enforce the seal at the LSM hook layer.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
Changes since v8:
- Extracted the ancestor-sealing walk out of landlock_append_fs_rule()
into a new seal_ancestors() helper, removing the out_unlock goto.
- The seal loop now handles LANDLOCK_WALK_INTERNAL explicitly (skips
disconnected internal-mount roots and keeps walking), matching the
updated landlock_walk_path_up() behavior.
- Broadened current_check_refer_path() enforcement:
deny_no_inherit_topology_change() is now applied to old_dentry and
(when present) new_dentry unconditionally, covering hard links (link)
in addition to rename/exchange.
- Factored the unlink/rmdir hooks into a new current_check_remove()
helper instead of duplicating the subject lookup and deny call.
- Simplified deny_no_inherit_topology_change() to short-circuit and log
on the first sealed layer, dropping the sealed_layers accumulator and
the redundant no_inherit test.
- Bumped the Landlock ABI to version 11 and updated the base_test
abi_version expectation.
- Rebased onto mic/next.
security/landlock/access.h | 6 +
security/landlock/fs.c | 156 ++++++++++++++++++-
security/landlock/ruleset.c | 30 +++-
security/landlock/ruleset.h | 13 ++
security/landlock/syscalls.c | 2 +-
tools/testing/selftests/landlock/base_test.c | 2 +-
6 files changed, 202 insertions(+), 7 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5..9df6c6de71e2 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -81,6 +81,12 @@ struct layer_mask {
*/
access_mask_t quiet : 1;
#endif /* CONFIG_AUDIT */
+ /**
+ * @no_inherit: Whether we have encountered a rule with the no-inherit
+ * flag for this layer, so that ancestor rules do not grant additional
+ * access rights.
+ */
+ access_mask_t no_inherit : 1;
} __packed __aligned(sizeof(access_mask_t));
/*
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 34d1c245af92..44ccf6cede85 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -362,6 +362,70 @@ static enum landlock_walk_result landlock_walk_path_up(struct path *const path)
return LANDLOCK_WALK_CONTINUE;
}
+/**
+ * seal_ancestors - Seal every ancestor of @walker up to the VFS root
+ *
+ * @ruleset: Ruleset (must be locked by the caller) to insert the seal rules
+ * into.
+ * @walker: Path to the inode whose ancestors must be sealed. It is walked up
+ * to the real VFS root and is modified during the walk.
+ *
+ * Inserts a no-access rule tagged @has_no_inherit_descendant for each ancestor
+ * so that topology-changing operations (rename, rmdir, link, ...) on them are
+ * denied.
+ *
+ * On failure the walk stops early, so the ruleset may be left with the leaf
+ * rule and only some of its ancestors sealed. The caller must therefore
+ * discard the ruleset on error rather than enforce it, since an enforced but
+ * partially sealed ruleset would provide a weaker guarantee than intended.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+static int seal_ancestors(struct landlock_ruleset *const ruleset,
+ struct path *const walker)
+{
+ int err = 0;
+
+ path_get(walker);
+ for (;;) {
+ struct landlock_rule *ancestor_rule;
+ struct landlock_id ancestor_id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+ const enum landlock_walk_result res =
+ landlock_walk_path_up(walker);
+
+ /* Done once the real VFS root has been reached. */
+ if (res == LANDLOCK_WALK_STOP_REAL_ROOT)
+ break;
+ /*
+ * @walker advanced past the disconnected root of an internal
+ * mount, which does not name a sealable ancestor; keep walking
+ * up so that every ancestor up to the real root is still
+ * sealed.
+ */
+ if (res == LANDLOCK_WALK_INTERNAL)
+ continue;
+
+ /* LANDLOCK_WALK_CONTINUE: seal this ancestor. */
+ ancestor_id.key.object =
+ get_inode_object(d_backing_inode(walker->dentry));
+ if (IS_ERR(ancestor_id.key.object)) {
+ err = PTR_ERR(ancestor_id.key.object);
+ break;
+ }
+ ancestor_rule = landlock_insert_rule(ruleset, ancestor_id, 0, 0);
+ landlock_put_object(ancestor_id.key.object);
+ if (IS_ERR(ancestor_rule)) {
+ err = PTR_ERR(ancestor_rule);
+ break;
+ }
+ ancestor_rule->layers[0].flags.has_no_inherit_descendant = true;
+ }
+ path_put(walker);
+ return err;
+}
+
/*
* @path: Should have been checked by get_path_from_fd().
*/
@@ -391,6 +455,20 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
mutex_lock(&ruleset->lock);
rule = landlock_insert_rule(ruleset, id, access_rights, flags);
err = PTR_ERR_OR_ZERO(rule);
+ /*
+ * Sealing an inode also seals all of its ancestors against topology
+ * changes, so walk the path up to the VFS root and seal each ancestor
+ * too. This is best effort: a concurrent rename during the walk may
+ * leave the seal incomplete. Such changes to the hierarchy between
+ * ruleset construction and enforcement are outside of Landlock's
+ * threat model. See the "Filesystem inheritance suppression" section
+ * in Documentation/userspace-api/landlock.rst for the limitations.
+ */
+ if (!err && (flags & LANDLOCK_ADD_RULE_NO_INHERIT)) {
+ struct path walker = *path;
+
+ err = seal_ancestors(ruleset, &walker);
+ }
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
@@ -1129,6 +1207,47 @@ log_fs_change_topology_dentry(const struct landlock_cred_security *const subject
});
}
+/**
+ * deny_no_inherit_topology_change - Deny topology changes on sealed paths
+ * @subject: Subject performing the operation.
+ * @dentry: Target of the topology modification.
+ *
+ * Return: -EACCES (and emits an audit record) if any of the subject's
+ * domain layers seal @dentry against topology changes: either @dentry
+ * itself has a %LANDLOCK_ADD_RULE_NO_INHERIT rule, or one of its
+ * descendants does (recorded via @has_no_inherit_descendant on the
+ * dentry's rule). Returns 0 otherwise.
+ */
+static int
+deny_no_inherit_topology_change(const struct landlock_cred_security *subject,
+ struct dentry *const dentry)
+{
+ const struct landlock_rule *rule;
+
+ if (WARN_ON_ONCE(!subject || !dentry || d_is_negative(dentry)))
+ return 0;
+
+ rule = find_rule(subject->domain, dentry);
+ if (!rule)
+ return 0;
+
+ for (size_t i = 0; i < rule->num_layers; i++) {
+ const struct landlock_layer *const layer = &rule->layers[i];
+
+ /*
+ * @has_no_inherit_descendant is a superset of @no_inherit: it is
+ * set on the rule's own object when the no-inherit rule is
+ * created, and on every ancestor. Testing it alone seals both.
+ */
+ if (layer->flags.has_no_inherit_descendant) {
+ log_fs_change_topology_dentry(subject, layer->level - 1,
+ dentry);
+ return -EACCES;
+ }
+ }
+ return 0;
+}
+
/**
* current_check_refer_path - Check if a rename or link action is allowed
*
@@ -1194,6 +1313,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
struct path old_parent_path;
struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {};
struct landlock_request request1 = {}, request2 = {};
+ int err;
if (!subject)
return 0;
@@ -1210,6 +1330,22 @@ static int current_check_refer_path(struct dentry *const old_dentry,
}
access_request_parent2 =
get_mode_access(d_backing_inode(old_dentry)->i_mode);
+ /*
+ * A no-inherit seal forbids any topology change of the sealed inode or
+ * its ancestors. Deny renaming or linking the source out of its
+ * hierarchy, as well as removing, overwriting or exchanging a sealed
+ * destination. This applies to both rename (@removable) and link
+ * (!@removable) operations, so it is checked unconditionally.
+ */
+ err = deny_no_inherit_topology_change(subject, old_dentry);
+ if (err)
+ return err;
+ if (!d_is_negative(new_dentry)) {
+ err = deny_no_inherit_topology_change(subject, new_dentry);
+ if (err)
+ return err;
+ }
+
if (removable) {
access_request_parent1 |= maybe_remove(old_dentry);
access_request_parent2 |= maybe_remove(new_dentry);
@@ -1586,16 +1722,32 @@ static int hook_path_symlink(const struct path *const dir,
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
}
+static int current_check_remove(const struct path *const dir,
+ struct dentry *const dentry,
+ const access_mask_t access_request)
+{
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs, NULL);
+
+ if (subject) {
+ int err = deny_no_inherit_topology_change(subject, dentry);
+
+ if (err)
+ return err;
+ }
+ return current_check_access_path(dir, access_request);
+}
+
static int hook_path_unlink(const struct path *const dir,
struct dentry *const dentry)
{
- return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
+ return current_check_remove(dir, dentry, LANDLOCK_ACCESS_FS_REMOVE_FILE);
}
static int hook_path_rmdir(const struct path *const dir,
struct dentry *const dentry)
{
- return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
+ return current_check_remove(dir, dentry, LANDLOCK_ACCESS_FS_REMOVE_DIR);
}
static int hook_path_truncate(const struct path *const path)
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index b8a35675bcbf..ca7cfa45c90a 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -258,6 +258,10 @@ insert_rule(struct landlock_ruleset *const ruleset,
return ERR_PTR(-EINVAL);
this->layers[0].access |= (*layers)[0].access;
this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
+ this->layers[0].flags.no_inherit |=
+ (*layers)[0].flags.no_inherit;
+ this->layers[0].flags.has_no_inherit_descendant |=
+ (*layers)[0].flags.has_no_inherit_descendant;
return this;
}
@@ -311,12 +315,20 @@ landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
const access_mask_t access, const u32 flags)
{
+ const bool no_inherit = !!(flags & LANDLOCK_ADD_RULE_NO_INHERIT);
struct landlock_layer layers[] = { {
.access = access,
/* When @level is zero, insert_rule() extends @ruleset. */
.level = 0,
.flags = {
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
+ .no_inherit = no_inherit,
+ /*
+ * The rule's own object is also sealed against
+ * topology changes, so mark it as if it had a
+ * no-inherit descendant.
+ */
+ .has_no_inherit_descendant = no_inherit,
},
} };
@@ -657,15 +669,25 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
*/
for (size_t i = 0; i < rule->num_layers; i++) {
const struct landlock_layer *const layer = &rule->layers[i];
+ struct layer_mask *const layer_mask =
+ &masks->layers[layer->level - 1];
+
+ /*
+ * Skip layers that already have no_inherit set: these layers
+ * should not inherit access rights from ancestor directories.
+ */
+ if (layer_mask->no_inherit)
+ continue;
/* Clear the bits where the layer in the rule grants access. */
- masks->layers[layer->level - 1].access &= ~layer->access;
+ layer_mask->access &= ~layer->access;
#ifdef CONFIG_AUDIT
- /* Collect rule flags for each layer. */
if (layer->flags.quiet)
- masks->layers[layer->level - 1].quiet = true;
+ layer_mask->quiet = true;
#endif /* CONFIG_AUDIT */
+ if (layer->flags.no_inherit)
+ layer_mask->no_inherit = true;
}
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
@@ -731,6 +753,7 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
+ masks->layers[i].no_inherit = false;
}
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
i++) {
@@ -738,6 +761,7 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
#ifdef CONFIG_AUDIT
masks->layers[i].quiet = false;
#endif /* CONFIG_AUDIT */
+ masks->layers[i].no_inherit = false;
}
return handled_accesses;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index c927bcb82fa3..8d3dd551bf77 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -40,6 +40,19 @@ struct landlock_layer {
* down the file hierarchy.
*/
u8 quiet : 1;
+ /**
+ * @no_inherit: Prevents this rule from inheriting access rights
+ * from ancestor inodes. Only used for filesystem rules; set
+ * via %LANDLOCK_ADD_RULE_NO_INHERIT.
+ */
+ u8 no_inherit : 1;
+ /**
+ * @has_no_inherit_descendant: Marker used to deny topology
+ * changes on the rule's object: either the object itself has
+ * a no-inherit rule, or a descendant does. Only used for
+ * filesystem rules; set by Landlock, never by user space.
+ */
+ u8 has_no_inherit_descendant : 1;
} flags;
/**
* @access: Bitfield of allowed actions on the kernel object. They are
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index b847b0be1cf7..3fea5492df0d 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -169,7 +169,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
-const int landlock_abi_version = 10;
+const int landlock_abi_version = 11;
/**
* sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index cbd3c1669951..b8b5fa1042ba 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(11, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
--
2.54.0
^ permalink raw reply related
* [PATCH v9 4/9] landlock: Move log_fs_change_topology_dentry() above current_check_refer_path()
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
In preparation for a new caller (the no-inherit topology-change check)
that sits earlier in fs.c, move log_fs_change_topology_dentry() above
current_check_refer_path() so that caller does not need a forward
declaration. Reflow its signature to match log_fs_change_topology_path()
while moving it.
No functional change intended.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
New patch in v9.
Splits the code motion out of the implementation patch: moves
log_fs_change_topology_dentry() above current_check_refer_path() so the
new no-inherit topology-change check does not need a forward
declaration. No functional change.
security/landlock/fs.c | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index fd829e06835d..34d1c245af92 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1115,6 +1115,20 @@ collect_domain_accesses(const struct landlock_ruleset *const domain,
return ret;
}
+static void
+log_fs_change_topology_dentry(const struct landlock_cred_security *const subject,
+ size_t handle_layer, struct dentry *const dentry)
+{
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
+ .audit = {
+ .type = LSM_AUDIT_DATA_DENTRY,
+ .u.dentry = dentry,
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+}
+
/**
* current_check_refer_path - Check if a rename or link action is allowed
*
@@ -1427,20 +1441,6 @@ log_fs_change_topology_path(const struct landlock_cred_security *const subject,
});
}
-static void log_fs_change_topology_dentry(
- const struct landlock_cred_security *const subject, size_t handle_layer,
- struct dentry *const dentry)
-{
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
- .audit = {
- .type = LSM_AUDIT_DATA_DENTRY,
- .u.dentry = dentry,
- },
- .layer_plus_one = handle_layer + 1,
- });
-}
-
/*
* Because a Landlock security policy is defined according to the filesystem
* topology (i.e. the mount namespace), changing it may grant access to files
--
2.54.0
^ permalink raw reply related
* [PATCH v9 3/9] landlock: Return inserted rule from landlock_insert_rule()
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
Change insert_rule() and landlock_insert_rule() to return the inserted
(or updated) struct landlock_rule pointer instead of an int errno.
Errors are propagated via ERR_PTR().
This gives callers a handle on the resulting rule so a subsequent change
can mutate per-layer flags on it (e.g. to mark ancestor rules created
for no-inherit topology sealing).
No functional change intended.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
Changes since v8:
- Simplified error propagation with PTR_ERR_OR_ZERO() in
landlock_append_fs_rule() and landlock_append_net_rule(), replacing
the open-coded IS_ERR()/PTR_ERR() handling.
- Rebased onto mic/next (the flags parameter is now u32).
security/landlock/fs.c | 6 ++--
security/landlock/net.c | 6 ++--
security/landlock/ruleset.c | 68 ++++++++++++++++++-------------------
security/landlock/ruleset.h | 7 ++--
4 files changed, 45 insertions(+), 42 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 5b9cc450d614..fd829e06835d 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -369,7 +369,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
access_mask_t access_rights, const u32 flags)
{
- int err;
+ int err = 0;
+ struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_INODE,
};
@@ -388,7 +389,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights, flags);
+ rule = landlock_insert_rule(ruleset, id, access_rights, flags);
+ err = PTR_ERR_OR_ZERO(rule);
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
diff --git a/security/landlock/net.c b/security/landlock/net.c
index cbff59ec3aba..88b9ffcd11fb 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -23,11 +23,11 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
const u16 port, access_mask_t access_rights,
const u32 flags)
{
- int err;
const struct landlock_id id = {
.key.data = (__force uintptr_t)htons(port),
.type = LANDLOCK_KEY_NET_PORT,
};
+ struct landlock_rule *rule;
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
@@ -36,10 +36,10 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
~landlock_get_net_access_mask(ruleset, 0);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights, flags);
+ rule = landlock_insert_rule(ruleset, id, access_rights, flags);
mutex_unlock(&ruleset->lock);
- return err;
+ return PTR_ERR_OR_ZERO(rule);
}
static int current_check_access_socket(struct socket *const sock,
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4dd09ea22c84..b8a35675bcbf 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -203,12 +203,13 @@ static void build_check_ruleset(void)
* added to @ruleset as new constraints, similarly to a boolean AND between
* access rights.
*
- * Return: 0 on success, -errno on failure.
+ * Return: A pointer to the inserted or updated rule, or an ERR_PTR on failure.
*/
-static int insert_rule(struct landlock_ruleset *const ruleset,
- const struct landlock_id id,
- const struct landlock_layer (*layers)[],
- const size_t num_layers)
+static struct landlock_rule *
+insert_rule(struct landlock_ruleset *const ruleset,
+ 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;
@@ -218,14 +219,14 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
might_sleep();
lockdep_assert_held(&ruleset->lock);
if (WARN_ON_ONCE(!layers))
- return -ENOENT;
+ return ERR_PTR(-ENOENT);
if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object))
- return -ENOENT;
+ return ERR_PTR(-ENOENT);
root = get_root(ruleset, id.type);
if (IS_ERR(root))
- return PTR_ERR(root);
+ return ERR_CAST(root);
walker_node = &root->rb_node;
while (*walker_node) {
@@ -243,7 +244,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
/* Only a single-level layer should match an existing rule. */
if (WARN_ON_ONCE(num_layers != 1))
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
/* If there is a matching rule, updates it. */
if ((*layers)[0].level == 0) {
@@ -252,16 +253,16 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
* landlock_add_rule(2), i.e. @ruleset is not a domain.
*/
if (WARN_ON_ONCE(this->num_layers != 1))
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
if (WARN_ON_ONCE(this->layers[0].level != 0))
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
this->layers[0].access |= (*layers)[0].access;
this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
- return 0;
+ return this;
}
if (WARN_ON_ONCE(this->layers[0].level == 0))
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
/*
* Intersects access rights when it is a merge between a
@@ -270,23 +271,23 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
new_rule = create_rule(id, &this->layers, this->num_layers,
&(*layers)[0]);
if (IS_ERR(new_rule))
- return PTR_ERR(new_rule);
+ return ERR_CAST(new_rule);
rb_replace_node(&this->node, &new_rule->node, root);
free_rule(this, id.type);
- return 0;
+ return new_rule;
}
/* There is no match for @id. */
build_check_ruleset();
if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
- return -E2BIG;
+ return ERR_PTR(-E2BIG);
new_rule = create_rule(id, layers, num_layers, NULL);
if (IS_ERR(new_rule))
- return PTR_ERR(new_rule);
+ return ERR_CAST(new_rule);
rb_link_node(&new_rule->node, parent_node, walker_node);
rb_insert_color(&new_rule->node, root);
ruleset->num_rules++;
- return 0;
+ return new_rule;
}
static void build_check_layer(void)
@@ -305,9 +306,10 @@ static void build_check_layer(void)
}
/* @ruleset must be locked by the caller. */
-int landlock_insert_rule(struct landlock_ruleset *const ruleset,
- const struct landlock_id id,
- const access_mask_t access, const u32 flags)
+struct landlock_rule *
+landlock_insert_rule(struct landlock_ruleset *const ruleset,
+ const struct landlock_id id,
+ const access_mask_t access, const u32 flags)
{
struct landlock_layer layers[] = { {
.access = access,
@@ -326,9 +328,8 @@ 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 landlock_rule *walker_rule, *next_rule, *rule;
struct rb_root *src_root;
- int err = 0;
might_sleep();
lockdep_assert_held(&dst->lock);
@@ -358,11 +359,11 @@ 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));
- if (err)
- return err;
+ rule = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
}
- return err;
+ return 0;
}
static int merge_ruleset(struct landlock_ruleset *const dst,
@@ -412,9 +413,8 @@ 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 landlock_rule *walker_rule, *next_rule, *rule;
struct rb_root *parent_root;
- int err = 0;
might_sleep();
lockdep_assert_held(&parent->lock);
@@ -432,12 +432,12 @@ static int inherit_tree(struct landlock_ruleset *const parent,
.type = key_type,
};
- err = insert_rule(child, id, &walker_rule->layers,
- walker_rule->num_layers);
- if (err)
- return err;
+ rule = insert_rule(child, id, &walker_rule->layers,
+ walker_rule->num_layers);
+ if (IS_ERR(rule))
+ return PTR_ERR(rule);
}
- return err;
+ return 0;
}
static int inherit_ruleset(struct landlock_ruleset *const parent,
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 61f3c253d5c9..c927bcb82fa3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -217,9 +217,10 @@ 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))
-int landlock_insert_rule(struct landlock_ruleset *const ruleset,
- const struct landlock_id id,
- const access_mask_t access, const u32 flags);
+struct landlock_rule *
+landlock_insert_rule(struct landlock_ruleset *const ruleset,
+ const struct landlock_id id,
+ const access_mask_t access, const u32 flags);
struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
--
2.54.0
^ permalink raw reply related
* [PATCH v9 2/9] landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
Wire up the new LANDLOCK_ADD_RULE_NO_INHERIT flag for
sys_landlock_add_rule(). Define the constant in the UAPI header with
its documentation, accept it from user space for
%LANDLOCK_RULE_PATH_BENEATH only, and update the path-beneath useless-
rule check so that an empty allowed_access is still accepted when a
flag (quiet or no-inherit) is present. Reject the flag with -EINVAL on
a ruleset that handles no filesystem access, since the resulting seal
would be inert.
The flag has no enforcement effect yet; that is added in a subsequent
patch.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
Changes since v8:
- Reordered ahead of "Return inserted rule from landlock_insert_rule()".
- Reject LANDLOCK_ADD_RULE_NO_INHERIT with -EINVAL when the ruleset
handles no filesystem access (the seal would be inert); documented the
new EINVAL case in the sys_landlock_add_rule() kerneldoc.
- Expanded the UAPI comment for LANDLOCK_ADD_RULE_NO_INHERIT: the
conservative seal (same-directory renames and hard links denied) and
the best-effort ancestor walk that is not serialized against rename.
- Rebased onto mic/next.
include/uapi/linux/landlock.h | 35 +++++++++++++++++++++++++++++++++++
security/landlock/syscalls.c | 25 ++++++++++++++++++++++---
2 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 7ffe2ef127ee..336b01dc43ec 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -123,10 +123,45 @@ struct landlock_ruleset_attr {
* allowed_access in the passed in rule_attr. When this flag is
* present, the caller is also allowed to pass in an empty
* allowed_access.
+ * %LANDLOCK_ADD_RULE_NO_INHERIT
+ * Disable the inheritance of access rights and flags from parent objects
+ * for the rule's object and its descendants.
+ *
+ * This flag currently applies only to filesystem rules. Passing it with
+ * any other rule type returns ``-EINVAL``.
+ *
+ * By default, Landlock filesystem rules inherit allowed accesses from
+ * ancestor directories: rights granted on a parent directory also apply
+ * to its children. A rule marked with %LANDLOCK_ADD_RULE_NO_INHERIT
+ * stops this propagation at its object; only the accesses explicitly
+ * allowed by the rule apply. Descendants of that object continue to
+ * inherit from it normally, unless they too carry this flag.
+ *
+ * This flag also enforces parent-directory restrictions: rename, rmdir,
+ * link, and other operations that would change the immediate parent of
+ * the rule's object or any of its ancestors are denied up to the VFS
+ * root. This prevents sandboxed processes from manipulating the
+ * filesystem hierarchy to evade restrictions (e.g. via sandbox-restart
+ * attacks). The seal is intentionally conservative: any rename, removal
+ * or link operation targeting a sealed object is denied, including
+ * same-directory renames and hard links that do not actually reparent it.
+ *
+ * Inheritance of rule flags (such as %LANDLOCK_ADD_RULE_QUIET) from
+ * ancestor directories is also blocked at the rule's object.
+ *
+ * Adding such a rule seals the rule's object and all of its ancestors up
+ * to the VFS root, so the kernel walks the path up to the VFS root while
+ * adding the rule, sealing each ancestor in turn. This walk is best
+ * effort: it is not serialized against concurrent renames, so a rename
+ * that reparents one of the ancestors while the walk is in progress may
+ * leave the seal incomplete. This is not a security concern: changes to
+ * the filesystem hierarchy between the time a ruleset is built and the
+ * time it is enforced are outside of Landlock's threat model.
*/
/* clang-format off */
#define LANDLOCK_ADD_RULE_QUIET (1U << 0)
+#define LANDLOCK_ADD_RULE_NO_INHERIT (1U << 1)
/* clang-format on */
/**
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f..b847b0be1cf7 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -361,7 +361,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
* are ignored in path walks. However, the rule is not useless if it is
- * there to hold a quiet flag.
+ * there to hold a quiet or no-inherit flag.
*/
if (!flags && !path_beneath_attr.allowed_access)
return -ENOMSG;
@@ -375,6 +375,15 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs)
return -EINVAL;
+ /*
+ * Checks for useless no-inherit flag: a seal is only ever consulted
+ * for a domain that handles some filesystem access, so a no-inherit
+ * rule added to a ruleset with no handled filesystem access would be
+ * silently inert.
+ */
+ if (flags & LANDLOCK_ADD_RULE_NO_INHERIT && !mask)
+ return -EINVAL;
+
/* Gets and checks the new rule. */
err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
if (err)
@@ -433,7 +442,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* @rule_type: Identify the structure type pointed to by @rule_attr:
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (matching the @rule_type).
- * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
+ * @flags: Bitmask of %LANDLOCK_ADD_RULE_* flags.
*
* This system call enables to define a new rule and add it to an existing
* ruleset.
@@ -451,6 +460,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
* - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no
* quiet access bits set for the corresponding rule type.
+ * - %EINVAL: LANDLOCK_ADD_RULE_NO_INHERIT is passed for a rule type
+ * that does not support it (e.g. %LANDLOCK_RULE_NET_PORT).
+ * - %EINVAL: LANDLOCK_ADD_RULE_NO_INHERIT is passed but the ruleset handles
+ * no filesystem access.
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
* 0) and no flags;
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
@@ -472,7 +485,13 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
if (!is_initialized())
return -EOPNOTSUPP;
- if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
+ /* Rejects unknown flags. */
+ if (flags & ~(LANDLOCK_ADD_RULE_QUIET | LANDLOCK_ADD_RULE_NO_INHERIT))
+ return -EINVAL;
+
+ /* LANDLOCK_ADD_RULE_NO_INHERIT only applies to path-beneath rules. */
+ if ((flags & LANDLOCK_ADD_RULE_NO_INHERIT) &&
+ rule_type != LANDLOCK_RULE_PATH_BENEATH)
return -EINVAL;
/* Gets and checks the ruleset. */
--
2.54.0
^ permalink raw reply related
* [PATCH v9 1/9] landlock: Add and use landlock_walk_path_up() helper
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
In-Reply-To: <20260621035223.2651547-1-utilityemal77@gmail.com>
Centralize the open-coded path-walk logic in fs.c by adding
landlock_walk_path_up(), which moves @path one step toward the VFS
root. Its return value indicates whether the new position is an
internal mount point, the real root, or neither (i.e. the caller
should continue walking).
Convert the two open-coded walks to the helper:
- is_access_to_paths_allowed() loses its backward goto.
- collect_domain_accesses() additionally changes its signature from
(mnt_root, dir) to a single struct path, so the caller's mount point
and starting dentry are both carried in @path, keeping the traversal
logic consistent between the two callers.
No functional change intended.
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
Changes since v8:
- Squashed the three v8 patches ("Add landlock_walk_path_up() helper",
"Use ... in is_access_to_paths_allowed()", and "Use ... in
collect_domain_accesses()") into this single patch.
- landlock_walk_path_up() now advances @path to the mount root before
returning LANDLOCK_WALK_INTERNAL, instead of leaving it on the
disconnected root, so callers can follow_up() into the parent mount.
Updated the enum landlock_walk_result kerneldoc to match.
- Reworded the current_check_refer_path() comment to explain that
i_rwsem is held and collect_domain_accesses() takes its own reference.
- Rebased onto mic/next.
security/landlock/fs.c | 176 ++++++++++++++++++++++++-----------------
1 file changed, 103 insertions(+), 73 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 6292887e6cef..5b9cc450d614 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -320,6 +320,48 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
/* clang-format on */
+/**
+ * enum landlock_walk_result - Result codes for landlock_walk_path_up()
+ * @LANDLOCK_WALK_CONTINUE: Path advanced one step and is now neither the real
+ * root nor the root of an internal mount point.
+ * @LANDLOCK_WALK_STOP_REAL_ROOT: Path has reached the real VFS root.
+ * @LANDLOCK_WALK_INTERNAL: Path advanced past the disconnected root of an
+ * internal mount point (e.g. nsfs) and now sits at that mount's root.
+ */
+enum landlock_walk_result {
+ LANDLOCK_WALK_CONTINUE,
+ LANDLOCK_WALK_STOP_REAL_ROOT,
+ LANDLOCK_WALK_INTERNAL,
+};
+
+static enum landlock_walk_result landlock_walk_path_up(struct path *const path)
+{
+ struct dentry *old;
+
+ while (path->dentry == path->mnt->mnt_root) {
+ if (!follow_up(path))
+ return LANDLOCK_WALK_STOP_REAL_ROOT;
+ }
+ old = path->dentry;
+ if (unlikely(IS_ROOT(old))) {
+ /*
+ * Reached a disconnected root: advance to the mount root so
+ * that the next call can follow_up() to the parent mount.
+ * Internal mounts (e.g. nsfs, reachable through
+ * /proc/<pid>/ns/<namespace>) are reported so that callers can
+ * stop the walk there.
+ */
+ path->dentry = dget(path->mnt->mnt_root);
+ dput(old);
+ if (likely(path->mnt->mnt_flags & MNT_INTERNAL))
+ return LANDLOCK_WALK_INTERNAL;
+ return LANDLOCK_WALK_CONTINUE;
+ }
+ path->dentry = dget_parent(old);
+ dput(old);
+ return LANDLOCK_WALK_CONTINUE;
+}
+
/*
* @path: Should have been checked by get_path_from_fd().
*/
@@ -889,46 +931,27 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
if (allowed_parent1 && allowed_parent2)
break;
-jump_up:
- if (walker_path.dentry == walker_path.mnt->mnt_root) {
- if (follow_up(&walker_path)) {
- /* Ignores hidden mount points. */
- goto jump_up;
- } else {
- /*
- * Stops at the real root. Denies access
- * because not all layers have granted access.
- */
- break;
- }
- }
-
- if (unlikely(IS_ROOT(walker_path.dentry))) {
- if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
- /*
- * Stops and allows access when reaching disconnected root
- * directories that are part of internal filesystems (e.g. nsfs,
- * which is reachable through /proc/<pid>/ns/<namespace>).
- */
- allowed_parent1 = true;
- allowed_parent2 = true;
- break;
- }
-
+ switch (landlock_walk_path_up(&walker_path)) {
+ case LANDLOCK_WALK_CONTINUE:
+ continue;
+ case LANDLOCK_WALK_INTERNAL:
/*
- * We reached a disconnected root directory from a bind mount.
- * Let's continue the walk with the mount point we missed.
+ * Stops and allows access when reaching disconnected
+ * root directories that are part of internal
+ * filesystems (e.g. nsfs, which is reachable through
+ * /proc/<pid>/ns/<namespace>).
*/
- dput(walker_path.dentry);
- walker_path.dentry = walker_path.mnt->mnt_root;
- dget(walker_path.dentry);
- } else {
- struct dentry *const parent_dentry =
- dget_parent(walker_path.dentry);
-
- dput(walker_path.dentry);
- walker_path.dentry = parent_dentry;
+ allowed_parent1 = true;
+ allowed_parent2 = true;
+ break;
+ case LANDLOCK_WALK_STOP_REAL_ROOT:
+ /*
+ * Stops at the real root. Denies access because not
+ * all layers have granted access.
+ */
+ break;
}
+ break;
}
path_put(&walker_path);
@@ -1019,48 +1042,51 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
* collect_domain_accesses - Walk through a file path and collect accesses
*
* @domain: Domain to check against.
- * @mnt_root: Last directory to check.
- * @dir: Directory to start the walk from.
+ * @path: Path to start the walk from and whose mount root is the last
+ * directory to check.
* @layer_masks_dom: Where to store the collected accesses.
*
- * This helper is useful to begin a path walk from the @dir directory to a
- * @mnt_root directory used as a mount point. This mount point is the common
- * ancestor between the source and the destination of a renamed and linked
- * file. While walking from @dir to @mnt_root, we record all the domain's
- * allowed accesses in @layer_masks_dom.
+ * This helper is useful to begin a path walk from @path to the mount root
+ * directory used as a mount point. This mount point is the common ancestor
+ * between the source and the destination of a renamed and linked file. While
+ * walking from @path to that mount root, we record all the domain's allowed
+ * accesses in @layer_masks_dom.
*
- * Because of disconnected directories, this walk may not reach @mnt_dir. In
- * this case, the walk will continue to @mnt_dir after this call.
+ * Because of disconnected directories, this walk may not reach that mount
+ * root. In this case, the walk will continue to the mount root after this
+ * call.
*
* This is similar to is_access_to_paths_allowed() but much simpler because it
* only handles walking on the same mount point and only checks one set of
* accesses.
*
- * Return: True if all the domain access rights are allowed for @dir, false if
- * the walk reached @mnt_root.
+ * Return: True if all the domain access rights are allowed for @path, false if
+ * the walk reached the mount root.
*/
-static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
- const struct dentry *const mnt_root,
- struct dentry *dir,
- struct layer_masks *layer_masks_dom)
+static bool
+collect_domain_accesses(const struct landlock_ruleset *const domain,
+ const struct path *const path,
+ struct layer_masks *layer_masks_dom)
{
bool ret = false;
+ struct path walker_path;
- if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
+ if (WARN_ON_ONCE(!domain || !path || !path->dentry || !path->mnt ||
+ !layer_masks_dom))
return true;
- if (is_nouser_or_private(dir))
+ if (is_nouser_or_private(path->dentry))
return true;
if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
layer_masks_dom, LANDLOCK_KEY_INODE))
return true;
- dget(dir);
+ walker_path = *path;
+ path_get(&walker_path);
while (true) {
- struct dentry *parent_dentry;
-
/* Gets all layers allowing all domain accesses. */
- if (landlock_unmask_layers(find_rule(domain, dir),
+ if (landlock_unmask_layers(find_rule(domain,
+ walker_path.dentry),
layer_masks_dom)) {
/*
* Stops when all handled accesses are allowed by at
@@ -1074,14 +1100,16 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
* Stops at the mount point or the filesystem root for a disconnected
* directory.
*/
- if (dir == mnt_root || unlikely(IS_ROOT(dir)))
+ if ((walker_path.dentry == path->mnt->mnt_root &&
+ walker_path.mnt == path->mnt) ||
+ unlikely(IS_ROOT(walker_path.dentry)))
break;
- parent_dentry = dget_parent(dir);
- dput(dir);
- dir = parent_dentry;
+ if (WARN_ON_ONCE(landlock_walk_path_up(&walker_path) !=
+ LANDLOCK_WALK_CONTINUE))
+ break;
}
- dput(dir);
+ path_put(&walker_path);
return ret;
}
@@ -1147,7 +1175,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
bool allow_parent1, allow_parent2;
access_mask_t access_request_parent1, access_request_parent2;
struct path mnt_dir;
- struct dentry *old_parent;
+ struct path old_parent_path;
struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {};
struct landlock_request request1 = {}, request2 = {};
@@ -1201,18 +1229,20 @@ static int current_check_refer_path(struct dentry *const old_dentry,
/*
* old_dentry may be the root of the common mount point and
* !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and
- * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because
- * we keep a reference to old_dentry.
+ * OPEN_TREE_CLONE). Reading old_dentry->d_parent without a reference is
+ * safe because the directories' i_rwsem are held across the hook;
+ * collect_domain_accesses() takes its own reference before walking.
*/
- old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry :
- old_dentry->d_parent;
+ old_parent_path.mnt = mnt_dir.mnt;
+ old_parent_path.dentry = (old_dentry == mnt_dir.dentry) ?
+ 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.dentry,
- old_parent,
+ allow_parent1 = collect_domain_accesses(subject->domain,
+ &old_parent_path,
&layer_masks_parent1);
- allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
- new_dir->dentry,
+ allow_parent2 = collect_domain_accesses(subject->domain, new_dir,
&layer_masks_parent2);
if (allow_parent1 && allow_parent2)
return 0;
@@ -1231,7 +1261,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
return 0;
if (request1.access) {
- request1.audit.u.path.dentry = old_parent;
+ request1.audit.u.path.dentry = old_parent_path.dentry;
landlock_log_denial(subject, &request1);
}
if (request2.access) {
--
2.54.0
^ permalink raw reply related
* [PATCH v9 0/9] Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2026-06-21 3:52 UTC (permalink / raw)
To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess
Hi,
This is version 9 of the LANDLOCK_ADD_RULE_NO_INHERIT series, which
implements a new flag to suppress inheritance of access rights and
flags from parent objects.
The main change this version is the rebase onto mic/next, now that
Tingmao Wang's "quiet flag" series, which earlier revisions depended
on has been merged (congrats!). This series is therefore no longer
based on it.
The rest is consolidation and hardening: the three path-walk patches
were squashed into one to avoid unused function build failures,
ancestor sealing and the removal hooks were factored into helpers, and
the topology seal was broadened to also deny hard links and
same-directory renames of a protected inode. The KUnit suite was
trimmed to the tests that exercise the skip branch through realistic
rule application, and a new EINVAL case rejects the flag on a
ruleset that handles no filesystem access.
Overall the code is larger; but mostly from test coverage and the
hardlink gap being closed.
Ancestor sealing:
The other big change is the clear documentation of the consistency
guarantees associated with this flag. One notable property of the
way ancestor sealing has been done in this series thus far, is that
that a concurrent rename of a path that is underneath the current
position during the upward ancestor sealing walk will result in
a partially undersealed and/or oversealed hierarchy.
To make this clearer, in seal_ancestors, we walk upwards inserting
blank rules / updating existing ones with the has_no_inherit_descendent
marker. So if /a/b/c/d is tagged with LANDLOCK_ADD_RULE_NO_INHERIT,
we walk upward inserting this marker on /a/b/c, /a/b, /a and /.
Say we are in the middle of marking /a/b/ and /a/b/c is moved to
/foo. The result is we continue to walk upward towards /,
but /foo is unsealed with no marker.
It is of my opinion (and this is not my call obviously) that this
inconsistency is unavoidable. Even if we check against the rename_lock
and serialize on it, nothing stops an outside process from moving
/a/b/c to /foo even AFTER the rule is fully inserted, resulting in
the same exact state. So protecting against this concurrent rename
at insertion time wouldn't actually achieve anything.
The only way to truly close this would be costly; moving sealing to
runtime, and having to track the movement of the sealed inode outside
the domain. That seems to me unacceptable and inconsistent with
Landlock's threat model.
Thus my judgement is that this topology sealing is sufficient as is;
outside processes can already tamper with inodes in a landlock ruleset,
by removing inodes referenced by the ruleset, bind mounts, and all sorts
of things.
It is clear that there is no guarantee the filesystem and thus the ruleset
at ruleset creation time and enforcement time will be consistent.
We handle the -ENOMEM case similarly, if we run into memory pressure,
and can't finish sealing, it is left to userspace to catch the error
and discard the potentially unsealed ruleset, or retry rule insertion.
The only difference now is both of these gotchas are now explicitly
documented in the code and user documentation.
Previous patch summary:
The new flag enables policies where a parent directory needs broader
access than its children. For example, a sandbox may permit read-write
access to /home/user but still prohibit writes to ~/.bashrc or
~/.ssh, even though they are nested beneath the parent. Today this is
not possible because access rights always propagate from parent to
child inodes.
When a rule is added with LANDLOCK_ADD_RULE_NO_INHERIT:
* access rights on parent inodes are ignored for that inode and its
descendants; and
* operations that reparent, rename, or remove the tagged inode or
its ancestors (via rename, rmdir, link) are denied up to the VFS
root; and
* parent flags do not propagate below a NO_INHERIT rule.
These parent-directory restrictions help mitigate sandbox-restart
attacks: a sandboxed process could otherwise move a protected
directory before exit, causing the next sandbox instance to apply its
policy to the wrong path.
Changes since v8:
1. Rebased onto mic/next now that Tingmao Wang's "quiet flag" series
has been merged; this series is no longer based on it.
2. Squashed the three landlock_walk_path_up() patches (add helper,
use in is_access_to_paths_allowed(), use in
collect_domain_accesses()) into a single patch.
3. landlock_walk_path_up() now advances to the mount root before
reporting an internal mount point, so the ancestor walk can
follow_up() into the parent mount instead of stopping at a
disconnected root. The sealing loop in landlock_append_fs_rule()
handles this LANDLOCK_WALK_INTERNAL case explicitly.
4. Added a preparatory patch moving log_fs_change_topology_dentry()
above current_check_refer_path(), and reordered the user-API
patch ahead of "Return inserted rule from landlock_insert_rule()".
5. Extracted ancestor sealing into a seal_ancestors() helper and the
unlink/rmdir hooks into a current_check_remove() helper, removing
the duplicated subject-lookup/deny blocks and the out_unlock goto.
6. Topology-change enforcement in current_check_refer_path() now
covers hard links (link) and same-directory renames in addition
to reparenting renames; deny_no_inherit_topology_change() short-
circuits and logs on the first sealed layer.
7. NO_INHERIT on a ruleset that handles no filesystem access is now
rejected with -EINVAL, with matching base_test coverage.
8. Selftests gained same-directory-rename and hard-link denial
assertions, REFER handling in the layered test, and two rejection
tests; the audit fixture dropped its redundant parent_is_logged
variant.
9. The KUnit suite was reduced from five tests to three, dropping the
two cases that set the per-layer no_inherit bit synthetically and
keeping those that exercise the skip branch through realistic rule
application.
10. Bumped the Landlock ABI to version 11 (updating base_test) and
expanded the UAPI and userspace-api documentation (conservative
seal, best-effort walk, threat model).
Changes since v7:
1. Replaced the v7 "Move find_rule definition above
landlock_append_fs_rule" preparatory patch with a new
preparatory patch that makes landlock_insert_rule() return the
inserted struct landlock_rule * via ERR_PTR(). The core
implementation patch now tags ancestor rules directly from the
return value, removing the find_rule() round trip after
insertion.
2. Folded the no_inherit / has_no_inherit_descendant bookkeeping
into the existing struct layer_mask as a single per-layer
no_inherit bit. The separate collected_rule_flags fields
(no_inherit_masks, no_inherit_desc_masks) are gone;
landlock_unmask_layers() now skips layers whose mask already has
no_inherit set, and landlock_init_layer_masks() clears the new
bit on initialization.
3. has_no_inherit_descendant is now auto-set on the rule's own
object when LANDLOCK_ADD_RULE_NO_INHERIT is passed, sealing it
against topology changes without requiring a separate blank-rule
insertion.
4. Centralized flag validation in sys_landlock_add_rule(): a single
mask check rejects unknown flags, and NO_INHERIT on any rule
type other than path-beneath is rejected at the syscall entry
point. The redundant per-rule-type NO_INHERIT check in
add_rule_net_port() was removed.
5. collect_domain_accesses() now takes a single struct path *
instead of separate mnt_root/dir parameters, matching
is_access_to_paths_allowed(). The disconnected-directory stop
condition was tightened with an explicit !d_unhashed() check at
the mount root.
6. deny_no_inherit_topology_change() dropped its override_layers
accumulator (it was always 0 in practice) and now just
OR-collects sealed layers.
7. Selftest coverage in fs_test.c was reorganized around fixtures
and variants: the v7 layout1 tests collapse into a
layout1_no_inherit fixture with five variants and three shared
tests; the four v7 layout4 mount tests collapse into a single
variant + test; and a new audit_no_inherit fixture replaces the
ad hoc audit case. Net change: 705 added lines in v7 -> 419
added lines in v8, with equivalent coverage.
8. The single KUnit test was expanded into five focused tests
covering propagation, skip, both-set, multi-layer, and
sequential-walk behavior of the per-layer no_inherit bit.
9. UAPI and userspace-api documentation reworded for clarity. The
new EINVAL case (NO_INHERIT on unsupported rule types) is
documented in the syscall kernel-doc.
10. Various commit messages reworded; switch arms in
is_access_to_paths_allowed() reordered so the fast path comes
first.
Changes since v6:
1. The main implementation of NO_INHERIT was split into smaller more
reviewable patches, separating the landlock_walk_path_up
implementation, usages of landlock_walk_path_up, and the find_rule
move to separate patches
2. A small issue regarding disconnected directory handling, where rules
inserted with NO_INHERIT only had protection up to a disconnected
directory instead of the mountpoint was fixed. In practice, this
isn't a problem at the current time since landlock forbids the mount
syscall needed to move a mountpoint with MS_MOVE. However, for
future-proofing in the case landlock allows some mount operations,
restrictions on parent directories now apply to the real root.
Changes since v5:
1. Retain existing documentation for path traversal in
is_access_to_paths_allowed.
2. Change conditional for path walk in is_access_to_paths_allowed
removing possibility of infinite loop and renamed constant.
3. Remove (now) redundant mnt_root parameter from
collect_domain_accesses.
4. Change path parameter to a dentry for
deny_no_inherit_topology_change because only the dentry was needed.
5. Remove duplicated tree diagram comment from selftests.
6. Minor documentation fixes.
Credit to Tingmao Wang for pointing out 1, 2, 3, 4, and 6.
Changes since v4:
1. Trimmed 120 lines from core implementation in fs.c.
2. Centralized path traversal logic with a helper function
landlock_walk_path_up.
3. Fixed bug in test on applying LANDLOCK_ADD_RULE_NO_INHERIT on
a file, giving it valid access rights.
4. Restructured commits to allow independent builds.
5. Adds userspace API documentation for the flag.
Changes since v3:
1. Trimmed core implementation in fs.c by removing redundant functions.
2. Fixed placement/inclusion of prototypes.
3. Added 4 new selftests for bind mount cases.
4. Protections now apply up to the VFS root instead of the mountpoint
root.
Links:
v1:
https://lore.kernel.org/linux-security-module/20251105180019.1432367-1-utilityemal77@gmail.com/
v2:
https://lore.kernel.org/linux-security-module/20251120222346.1157004-1-utilityemal77@gmail.com/
v3:
https://lore.kernel.org/linux-security-module/20251126122039.3832162-1-utilityemal77@gmail.com/
v4:
https://lore.kernel.org/linux-security-module/20251207015132.800576-1-utilityemal77@gmail.com/
v5:
https://lore.kernel.org/linux-security-module/20251214170548.408142-1-utilityemal77@gmail.com/
v6:
https://lore.kernel.org/linux-security-module/20260118000000.000000-1-utilityemal77@gmail.com/
v7:
https://lore.kernel.org/linux-security-module/20260412193214.87072-1-utilityemal77@gmail.com/
v8:
https://lore.kernel.org/linux-security-module/20260529015210.500291-1-utilityemal77@gmail.com/
Example usage:
# LL_FS_RO="/a/b/c" LL_FS_RW="/" LL_FS_NO_INHERIT="/a/b/c"
landlock-sandboxer sh
# touch /a/b/c/fi # denied; / RW does not inherit
# rmdir /a/b/c # denied by ancestor protections
# mv /a /bad # denied
# mkdir /a/good; touch /a/good/fi # allowed; unrelated path
All tests added by this series, and all other existing landlock tests,
are passing. This patch was also validated through checkpatch.pl --strict
with no complaints.
Special thanks to Tingmao Wang and Mickaël Salaün for your valuable
feedback.
Thank you for your time and review.
Regards,
Justin Suess
Justin Suess (9):
landlock: Add and use landlock_walk_path_up() helper
landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API
landlock: Return inserted rule from landlock_insert_rule()
landlock: Move log_fs_change_topology_dentry() above
current_check_refer_path()
landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
landlock: Add documentation for LANDLOCK_ADD_RULE_NO_INHERIT
samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to
landlock-sandboxer
selftests/landlock: Add selftests for LANDLOCK_ADD_RULE_NO_INHERIT
landlock: Add KUnit tests for LANDLOCK_ADD_RULE_NO_INHERIT
Documentation/userspace-api/landlock.rst | 44 ++
include/uapi/linux/landlock.h | 35 ++
samples/landlock/sandboxer.c | 16 +-
security/landlock/access.h | 6 +
security/landlock/fs.c | 366 ++++++++++++----
security/landlock/net.c | 6 +-
security/landlock/ruleset.c | 235 ++++++++--
security/landlock/ruleset.h | 20 +-
security/landlock/syscalls.c | 27 +-
tools/testing/selftests/landlock/base_test.c | 60 ++-
tools/testing/selftests/landlock/fs_test.c | 425 +++++++++++++++++++
11 files changed, 1100 insertions(+), 140 deletions(-)
base-commit: 1c236e7fe740a009ad8dd40a5ee0602ec402fffe
--
2.54.0
^ permalink raw reply
* [syzbot] Monthly tomoyo report (Jun 2026)
From: syzbot @ 2026-06-20 12:32 UTC (permalink / raw)
To: linux-kernel, linux-security-module, penguin-kernel,
syzkaller-bugs, takedakn, tomoyo-users_en
Hello tomoyo maintainers/developers,
This is a 31-day syzbot report for the tomoyo subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/tomoyo
During the period, 1 new issues were detected and 0 were fixed.
In total, 3 issues are still open and 12 have already been fixed.
Some of the still happening issues:
Ref Crashes Repro Title
<1> 558 Yes INFO: rcu detected stall in sys_newfstatat (4)
https://syzkaller.appspot.com/bug?extid=1c02a56102605204445c
<2> 1 No KMSAN: uninit-value in tomoyo_path_chown (3)
https://syzkaller.appspot.com/bug?extid=eaae8fa60ce81f1e4eeb
<3> 100 Yes INFO: rcu detected stall in sys_symlinkat (5)
https://syzkaller.appspot.com/bug?extid=9618c3b34a3062164a21
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders
To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem
You may send multiple commands in a single email message.
^ permalink raw reply
* Re: [GIT PULL] Landlock update for v7.2-rc1
From: pr-tracker-bot @ 2026-06-19 19:25 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Linus Torvalds, Mickaël Salaün, Bryam Vargas,
Günther Noack, Günther Noack, Justin Suess,
Matthieu Buffet, Maximilian Heyne, Tingmao Wang, linux-kernel,
linux-security-module
In-Reply-To: <20260619083504.1779997-1-mic@digikod.net>
The pull request you sent on Fri, 19 Jun 2026 10:35:04 +0200:
> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git tags/landlock-7.2-rc1
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/5e2e14749c3d969e263a879db104db6e9f0eb484
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* Re: [PATCH] LSM: check if lsmprop_to_secctx call is supported by LSM
From: Casey Schaufler @ 2026-06-19 17:44 UTC (permalink / raw)
To: Sebastian Bockholt, linux-security-module
Cc: serge, jmorris, paul, Casey Schaufler
In-Reply-To: <20260619171945.1617498-1-sebastian.bockholt@mail.networkname.de>
On 6/19/2026 10:19 AM, Sebastian Bockholt wrote:
> In include/linux/lsm_hook_defs.h, lsmprop_to_secctx is defined with
> a default return value of -EOPNOTSUPP.
> The function bpf_lsm_lsmprop_to_secctx, defined in
> security/bpf/hooks.c, returns the hook's default value. Therefore,
> directly returning the result of the bpf_lsm_lsmprop_to_secctx call
> propagates an unchecked EOPNOTSUPP error.
If the BPF LSM (the BPF LSM infrastructure, not the eBPF programs)
is going to support security contexts you need to mark it
LSM_FLAGS_EXCLUSIVE. Sorry, but the work to support multiple LSMs
that use security contexts is not progressing at a brisk pace.
Until then your choices are:
- Make the BPF LSM exclusive
- Do not use any of the security context or secid based hooks
If you want to help with the multiple LSM support, there's still
plenty of work to do. Let me know.
>
> Signed-off-by: Sebastian Bockholt <sebastian.bockholt@bevuta.com>
> ---
> security/security.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/security/security.c b/security/security.c
> index 71aea8fdf014..9c63699d45fc 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -3954,12 +3954,16 @@ EXPORT_SYMBOL(security_secid_to_secctx);
> int security_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp,
> int lsmid)
> {
> + int error;
> struct lsm_static_call *scall;
>
> lsm_for_each_hook(scall, lsmprop_to_secctx) {
> if (lsmid != LSM_ID_UNDEF && lsmid != scall->hl->lsmid->id)
> continue;
> - return scall->hl->hook.lsmprop_to_secctx(prop, cp);
> + error = scall->hl->hook.lsmprop_to_secctx(prop, cp);
> + if (error == -EOPNOTSUPP)
> + continue;
> + return error;
> }
> return LSM_RET_DEFAULT(lsmprop_to_secctx);
> }
^ permalink raw reply
* [PATCH] LSM: check if lsmprop_to_secctx call is supported by LSM
From: Sebastian Bockholt @ 2026-06-19 17:19 UTC (permalink / raw)
To: linux-security-module; +Cc: serge, jmorris, paul, Sebastian Bockholt
In include/linux/lsm_hook_defs.h, lsmprop_to_secctx is defined with
a default return value of -EOPNOTSUPP.
The function bpf_lsm_lsmprop_to_secctx, defined in
security/bpf/hooks.c, returns the hook's default value. Therefore,
directly returning the result of the bpf_lsm_lsmprop_to_secctx call
propagates an unchecked EOPNOTSUPP error.
Signed-off-by: Sebastian Bockholt <sebastian.bockholt@bevuta.com>
---
security/security.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..9c63699d45fc 100644
--- a/security/security.c
+++ b/security/security.c
@@ -3954,12 +3954,16 @@ EXPORT_SYMBOL(security_secid_to_secctx);
int security_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp,
int lsmid)
{
+ int error;
struct lsm_static_call *scall;
lsm_for_each_hook(scall, lsmprop_to_secctx) {
if (lsmid != LSM_ID_UNDEF && lsmid != scall->hl->lsmid->id)
continue;
- return scall->hl->hook.lsmprop_to_secctx(prop, cp);
+ error = scall->hl->hook.lsmprop_to_secctx(prop, cp);
+ if (error == -EOPNOTSUPP)
+ continue;
+ return error;
}
return LSM_RET_DEFAULT(lsmprop_to_secctx);
}
--
2.54.0
^ permalink raw reply related
* [PATCH 1/2] bpf: lsm: disable xfrm_decode_session hook attachment
From: Bradley Morgan @ 2026-06-19 13:03 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: linux-kernel, Bradley Morgan, stable, KP Singh, Matt Bobrowski,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Florent Revest, Brendan Jackman
BPF LSM programs can currently attach to xfrm_decode_session(). That
hook may return an error, but security_skb_classify_flow() calls it
from a void path and triggers BUG_ON() if an error is returned.
Disable BPF attachment to the hook to prevent a BPF LSM program from
turning packet classification into a full panic.
Fixes: 9e4e01dfd325 ("bpf: lsm: Implement attach, detach and execution")
Cc: stable@vger.kernel.org
Signed-off-by: Bradley Morgan <include@grrlz.net>
---
kernel/bpf/bpf_lsm.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 564071a92d7d..1433809bb166 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -51,6 +51,9 @@ BTF_ID(func, bpf_lsm_key_getsecurity)
#ifdef CONFIG_AUDIT
BTF_ID(func, bpf_lsm_audit_rule_match)
#endif
+#ifdef CONFIG_SECURITY_NETWORK_XFRM
+BTF_ID(func, bpf_lsm_xfrm_decode_session)
+#endif
BTF_ID(func, bpf_lsm_ismaclabel)
BTF_ID(func, bpf_lsm_file_alloc_security)
BTF_SET_END(bpf_lsm_disabled_hooks)
--
2.53.0
^ permalink raw reply related
* [PATCH 2/2] lsm: fix size queries for getselfattr with NULL buffer
From: Bradley Morgan @ 2026-06-19 13:03 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: linux-kernel, Bradley Morgan, stable, Paul Moore, James Morris,
Serge E. Hallyn, Shuah Khan, linux-kselftest
In-Reply-To: <20260619130305.27779-1-include@grrlz.net>
The lsm_get_self_attr() syscall allows callers to pass in a NULL context
buffer to find out the size of the output needed. That path still
compared the computed entry size against the caller provided size first,
so a NULL buffer with size 0 incorrectly returned -E2BIG rather than
reporting the required size.
Only enforce the available buffer length after checking for the NULL
buffer. Cover the zero length sizing query in the self test.
Fixes: d7cf3412a9f6 ("lsm: consolidate buffer size handling into lsm_fill_user_ctx()")
Cc: stable@vger.kernel.org
Signed-off-by: Bradley Morgan <include@grrlz.net>
---
security/security.c | 8 ++++----
tools/testing/selftests/lsm/lsm_get_self_attr_test.c | 5 ++---
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..fa0d7e036249 100644
--- a/security/security.c
+++ b/security/security.c
@@ -406,15 +406,15 @@ int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
int rc = 0;
nctx_len = ALIGN(struct_size(nctx, ctx, val_len), sizeof(void *));
+ /* no buffer - return success/0 and set @uctx_len to the req size */
+ if (!uctx)
+ goto out;
+
if (nctx_len > *uctx_len) {
rc = -E2BIG;
goto out;
}
- /* no buffer - return success/0 and set @uctx_len to the req size */
- if (!uctx)
- goto out;
-
nctx = kzalloc(nctx_len, GFP_KERNEL);
if (nctx == NULL) {
rc = -ENOMEM;
diff --git a/tools/testing/selftests/lsm/lsm_get_self_attr_test.c b/tools/testing/selftests/lsm/lsm_get_self_attr_test.c
index 60caf8528f81..2f5ababc2b95 100644
--- a/tools/testing/selftests/lsm/lsm_get_self_attr_test.c
+++ b/tools/testing/selftests/lsm/lsm_get_self_attr_test.c
@@ -39,15 +39,14 @@ TEST(size_null_lsm_get_self_attr)
TEST(ctx_null_lsm_get_self_attr)
{
- const long page_size = sysconf(_SC_PAGESIZE);
- __u32 size = page_size;
+ __u32 size = 0;
int rc;
rc = lsm_get_self_attr(LSM_ATTR_CURRENT, NULL, &size, 0);
if (attr_lsm_count()) {
ASSERT_NE(-1, rc);
- ASSERT_NE(1, size);
+ ASSERT_NE(0, size);
} else {
ASSERT_EQ(-1, rc);
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Christian Brauner @ 2026-06-19 10:25 UTC (permalink / raw)
To: David Windsor
Cc: viro, jack, ast, daniel, john.fastabend, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, kpsingh,
mattbobrowski, paul, jmorris, serge, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, stephen.smalley.work, omosnace,
casey, shuah, linux-kernel, linux-fsdevel, bpf,
linux-security-module, linux-integrity, selinux, linux-kselftest
In-Reply-To: <20260618203411.73917-2-dwindsor@gmail.com>
On Thu, Jun 18, 2026 at 04:34:10PM -0400, David Windsor wrote:
> Add bpf_init_inode_xattr() kfunc for BPF LSM programs to atomically set
> xattrs via the inode_init_security hook using lsm_get_xattr_slot().
>
> The inode_init_security hook previously took the xattr array and count
> as two separate output parameters (struct xattr *xattrs, int
> *xattr_count), which BPF programs cannot write to. Pass the xattr state
> as a single context object (struct xattr_ctx) instead, and have
> bpf_init_inode_xattr() take that context directly. Update the existing
> in-tree callers of inode_init_security to take and forward the new
> xattr_ctx.
>
> A previous attempt [1] required a kmalloc string output protocol for
> the xattr name. Since commit 6bcdfd2cac55 ("security: Allow all LSMs to
> provide xattrs for inode_init_security hook") [2], the xattr name is no
> longer allocated; it is a static constant.
>
> Because we rely on the hook-specific ctx layout, the kfunc is
> restricted to lsm/inode_init_security. Restrict the xattr names that
> may be set via this kfunc to the bpf.* namespace.
>
> Link: https://kernsec.org/pipermail/linux-security-module-archive/2022-October/034878.html [1]
> Link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6bcdfd2cac55 [2]
> Suggested-by: Song Liu <song@kernel.org>
> Signed-off-by: David Windsor <dwindsor@gmail.com>
> ---
> fs/bpf_fs_kfuncs.c | 106 +++++++++++++++++++++++++++++-
Please split this into the VFS changes and lsm changes required for
this. The api change to the lsm layer can be done independently of any
of the actual VFS level wiring. Will also make it a lot nicer to
review...
^ permalink raw reply
* Re: [PATCH v5 7/8] vfs: Replace security_sb_mount/security_move_mount with granular hooks
From: Christian Brauner @ 2026-06-19 10:17 UTC (permalink / raw)
To: Song Liu
Cc: linux-security-module, linux-fsdevel, selinux, apparmor, paul,
jmorris, serge, viro, jack, john.johansen, stephen.smalley.work,
omosnace, mic, gnoack, takedakn, penguin-kernel, herton,
kernel-team
In-Reply-To: <178179134209.111814.12159888808546010170.b4-reply@b4>
On Thu, Jun 18, 2026 at 04:02:22PM +0200, Christian Brauner wrote:
> On 2026-06-18 18:56:42+08:00, Song Liu wrote:
> > On Wed, Jun 17, 2026 at 9:53 PM Christian Brauner <brauner@kernel.org> wrote:
> >
> > > On Thu, May 28, 2026 at 11:26:06AM -0700, Song Liu wrote:
> >
> > [...]
> >
> > > >
> > >
> > > This again is racy as it is called outside of the namespace semaphore:
> > >
> > > err = security_mount_bind(&old_path, path, recurse);
> > > if (err)
> > > return err;
> > >
> > > if (mnt_ns_loop(old_path.dentry))
> > > return -EINVAL;
> > >
> > > LOCK_MOUNT(mp, path);
> > > if (IS_ERR(mp.parent))
> > > return PTR_ERR(mp.parent);
> > >
> > > After LOCK_MOUNT @path might point to a completely different mount then
> > > the one you performed your security checks on.
> >
> > I thought we agreed at LSF/MM/BPF 2026 to add the LSM hooks
> > before taking namespace semaphore, so that it is possible for LSMs
> > to defend against DoS attacks on namespace semaphore? Did I
> > miss/misunderstand something?
>
> I think there was a misunderstanding. What I pointed out was that it's a
> trade-off. If we do call security hooks under the namespace semaphore or
> mount lock than anything that's called under there must take care to not
> cause deadlocks - which is especially easy to do with mount lock and
> even with the namespace semaphore it may get hairy (automounts etc). The
> dos thing is another worry but if an LSM does stupid things we tell it
> to not do stupid things and to go away.
>
> But as the hooks are done right now they are meaningless from a security
> perspective. You might have a policy that allows mounting on dentry_a
> and deny mounting on dentry_b: before LOCK_MOUNT*() you may see dentry_a
> and allow the mount but after LOCK_MOUNT*() someone raced you and shoved
> a dentry_b mount onto dentry_b and now you allow overmounting dentry_b
*a dentry_b mount onto dentry_a
^ permalink raw reply
* Re: [PATCH] selftests/landlock: explicitly disable audit
From: Maximilian Heyne @ 2026-06-19 9:09 UTC (permalink / raw)
To: Mickaël Salaün
Cc: stable, Günther Noack, Shuah Khan, linux-security-module,
linux-kselftest, linux-kernel
In-Reply-To: <20260619.Ang7AiGeishu@digikod.net>
Hi Mickaël,
On Fri, Jun 19, 2026 at 10:32:45AM +0200, Mickaël Salaün wrote:
> I extended your patch and merged it:
> https://git.kernel.org/mic/c/next&id=0302cd72fe196aee933e3fb76f6d175d1ab0e843
>
> Thanks!
Thank you! Sorry for the late response. Only yesterday I tried the
patches you pointed me at and they also helped in my setup. I was also
about to sent a patch regarding filtering out the domain deallocation
records but that was also covered by you already.
>
> On Tue, Jun 09, 2026 at 12:51:03AM +0200, Mickaël Salaün wrote:
> > Thanks for this patch. I merged a few fixes and I'd be interested to
> > know if this one fix the issue you spotted:
> > https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/commit/?h=next&id=d8dfb4c7faa87c3e41a8678f38f136c2c7c036fa
> >
> >
> > On Fri, May 29, 2026 at 08:03:41PM +0000, Maximilian Heyne wrote:
> > > I'm seeing sporadic selftest failures, such as
> > >
> > > # RUN scoped_audit.connect_to_child ...
> > > # scoped_abstract_unix_test.c:314:connect_to_child:Expected 0 (0) == records.access (8)
> > > # connect_to_child: Test failed
> > > # FAIL scoped_audit.connect_to_child
> > > not ok 19 scoped_audit.connect_to_child
> > >
> > > This seems similar to what commit 3647a4977fb73d ("selftests/landlock:
> > > Drain stale audit records on init") tried to fix. However, the added
> > > drain loop is not effective. When setting the AUDIT_STATUS_PID, the
> > > kauditd_thread is woken up starting to send messages from the hold queue
> > > to the netlink. Depending on scheduling of this kthread not all messages
> > > might be send via the netlink in the 1 us interval.
> > >
> > > Therefore, instead of trying to drain the queue, let's just disable
> > > audit when running non-audit tests or more precisely disable it after
> > > audit-tests. This way we won't generate any new audit message that could
> > > interfere with the other tests.
> > >
> > > The comment saying that on process exit audit will be disabled is wrong.
> > > The closed file descriptor just causes an auditd_reset(), not a
> > > disablement. So future messages will be queued in the hold queue.
> > >
> > > Cc: stable@vger.kernel.org
> > > Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
> > > Signed-off-by: Maximilian Heyne <mheyne@amazon.de>
> > > ---
> > >
> > > I've seen the failures on the 6.18 kernels but haven't tested on latest
> > > upstream. However, I still think this is an issue.
> > >
> > > ---
> > > tools/testing/selftests/landlock/audit.h | 13 +++++--------
> > > 1 file changed, 5 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
> > > index 834005b2b0f09..7842330875f53 100644
> > > --- a/tools/testing/selftests/landlock/audit.h
> > > +++ b/tools/testing/selftests/landlock/audit.h
> > > @@ -494,10 +494,9 @@ static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
> > > static int audit_cleanup(int audit_fd, struct audit_filter *filter)
> >
> > audit_cleanup() should be called for audit_exec tests too.
> >
> > > {
> > > struct audit_filter new_filter;
> > > + int err;
> > >
> > > if (audit_fd < 0 || !filter) {
> > > - int err;
> > > -
> > > /*
> > > * Simulates audit_init_with_exe_filter() when called from
> > > * FIXTURE_TEARDOWN_PARENT().
> > > @@ -518,12 +517,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter)
> > > audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE);
> > > audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
> > >
> > > - /*
> > > - * Because audit_cleanup() might not be called by the test auditd
> > > - * process, it might not be possible to explicitly set it. Anyway,
> > > - * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
> > > - * process will exit.
> > > - */
> >
> > Please add a comment that explains that the audit state is not restored
> > but just disabled.
> >
> > > + err = audit_set_status(audit_fd, AUDIT_STATUS_ENABLED, 0);
> > > + if (err)
> > > + return err;
> > > +
> > > return close(audit_fd);
> >
> > FDs should always be closed.
> >
> > > }
> > >
> > > --
> > > 2.50.1
> > >
> > >
> > >
> > >
> > > Amazon Web Services Development Center Germany GmbH
> > > Tamara-Danz-Str. 13
> > > 10243 Berlin
> > > Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
> > > Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
> > > Sitz: Berlin
> > > Ust-ID: DE 365 538 597
> > >
> > >
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply
* [GIT PULL] Landlock update for v7.2-rc1
From: Mickaël Salaün @ 2026-06-19 8:35 UTC (permalink / raw)
To: Linus Torvalds
Cc: Mickaël Salaün, Bryam Vargas, Günther Noack,
Günther Noack, Justin Suess, Matthieu Buffet,
Maximilian Heyne, Tingmao Wang, linux-kernel,
linux-security-module
Hi,
This PR adds new Landlock access rights to control UDP bind and
connect/send operations, and a new "quiet" feature to mute specific
audit logs (and other future observability events). A few commits also
fix Landlock issues.
Please pull these changes for v7.2-rc1 . These commits merge cleanly
with your master branch. Most kernel changes have been tested in the
latest linux-next releases for some weeks, and I waited a bit more since
last week to make sure the changes brought by the recently squashed
fixes are ok.
Test coverage for security/landlock is 91.5% of 2351 lines according to
LLVM 22, and it was 90.9% of 2176 lines before this PR.
syzkaller changes have been developed to cover these new features:
https://github.com/google/syzkaller/pull/7493
Regards,
Mickaël
--
The following changes since commit 5d6919055dec134de3c40167a490f33c74c12581:
Linux 7.1-rc3 (2026-05-10 14:08:09 -0700)
are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git tags/landlock-7.2-rc1
for you to fetch changes up to 1c236e7fe740a009ad8dd40a5ee0602ec402fffe:
selftests/landlock: Add tests for invalid use of quiet flag (2026-06-14 20:17:25 +0200)
----------------------------------------------------------------
Landlock update for v7.2-rc1
----------------------------------------------------------------
Bryam Vargas (2):
landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path
selftests/landlock: Test SCOPE_SIGNAL on the SIGIO/fowner pgid path
Matthieu Buffet (7):
landlock: Fix unmarked concurrent access to socket family
landlock: Add UDP bind() access control
landlock: Add UDP send+connect access control
selftests/landlock: Add tests for UDP bind/connect
selftests/landlock: Add tests for UDP send
samples/landlock: Add sandboxer UDP access control
landlock: Add documentation for UDP support
Maximilian Heyne (1):
selftests/landlock: Explicitly disable audit in teardowns
Mickaël Salaün (5):
selftests/landlock: Filter dealloc records in audit_count_records()
selftests/landlock: Increase default audit socket timeout
landlock: Set audit_net.sk for socket access checks
landlock: Account all audit data allocations to user space
landlock: Demonstrate best-effort allowed_access filtering
Tingmao Wang (9):
landlock: Add a place for flags to layer rules
landlock: Add API support and docs for the quiet flags
landlock: Suppress logging when quiet flag is present
samples/landlock: Add quiet flag support to sandboxer
selftests/landlock: Replace hard-coded 16 with a constant
selftests/landlock: Add tests for quiet flag with fs rules
selftests/landlock: Add tests for quiet flag with net rules
selftests/landlock: Add tests for quiet flag with scope
selftests/landlock: Add tests for invalid use of quiet flag
Documentation/admin-guide/LSM/landlock.rst | 13 +-
Documentation/userspace-api/landlock.rst | 145 +-
include/uapi/linux/landlock.h | 97 +-
samples/landlock/sandboxer.c | 175 +-
security/landlock/access.h | 44 +-
security/landlock/audit.c | 292 ++-
security/landlock/audit.h | 3 +-
security/landlock/domain.c | 66 +-
security/landlock/domain.h | 16 +-
security/landlock/fs.c | 171 +-
security/landlock/fs.h | 29 +-
security/landlock/limits.h | 5 +-
security/landlock/net.c | 185 +-
security/landlock/net.h | 5 +-
security/landlock/ruleset.c | 49 +-
security/landlock/ruleset.h | 29 +-
security/landlock/syscalls.c | 73 +-
security/landlock/task.c | 11 +
tools/testing/selftests/landlock/audit.h | 140 +-
tools/testing/selftests/landlock/audit_test.c | 33 +-
tools/testing/selftests/landlock/base_test.c | 122 +-
tools/testing/selftests/landlock/common.h | 2 +
tools/testing/selftests/landlock/fs_test.c | 2445 +++++++++++++++++++-
tools/testing/selftests/landlock/net_test.c | 1392 ++++++++++-
tools/testing/selftests/landlock/ptrace_test.c | 1 +
.../selftests/landlock/scoped_abstract_unix_test.c | 78 +-
.../selftests/landlock/scoped_signal_test.c | 182 ++
27 files changed, 5368 insertions(+), 435 deletions(-)
^ permalink raw reply
* Re: [PATCH] selftests/landlock: explicitly disable audit
From: Mickaël Salaün @ 2026-06-19 8:32 UTC (permalink / raw)
To: Maximilian Heyne
Cc: stable, Günther Noack, Shuah Khan, linux-security-module,
linux-kselftest, linux-kernel
In-Reply-To: <20260604.Gee4caexei8o@digikod.net>
I extended your patch and merged it:
https://git.kernel.org/mic/c/next&id=0302cd72fe196aee933e3fb76f6d175d1ab0e843
Thanks!
On Tue, Jun 09, 2026 at 12:51:03AM +0200, Mickaël Salaün wrote:
> Thanks for this patch. I merged a few fixes and I'd be interested to
> know if this one fix the issue you spotted:
> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/commit/?h=next&id=d8dfb4c7faa87c3e41a8678f38f136c2c7c036fa
>
>
> On Fri, May 29, 2026 at 08:03:41PM +0000, Maximilian Heyne wrote:
> > I'm seeing sporadic selftest failures, such as
> >
> > # RUN scoped_audit.connect_to_child ...
> > # scoped_abstract_unix_test.c:314:connect_to_child:Expected 0 (0) == records.access (8)
> > # connect_to_child: Test failed
> > # FAIL scoped_audit.connect_to_child
> > not ok 19 scoped_audit.connect_to_child
> >
> > This seems similar to what commit 3647a4977fb73d ("selftests/landlock:
> > Drain stale audit records on init") tried to fix. However, the added
> > drain loop is not effective. When setting the AUDIT_STATUS_PID, the
> > kauditd_thread is woken up starting to send messages from the hold queue
> > to the netlink. Depending on scheduling of this kthread not all messages
> > might be send via the netlink in the 1 us interval.
> >
> > Therefore, instead of trying to drain the queue, let's just disable
> > audit when running non-audit tests or more precisely disable it after
> > audit-tests. This way we won't generate any new audit message that could
> > interfere with the other tests.
> >
> > The comment saying that on process exit audit will be disabled is wrong.
> > The closed file descriptor just causes an auditd_reset(), not a
> > disablement. So future messages will be queued in the hold queue.
> >
> > Cc: stable@vger.kernel.org
> > Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
> > Signed-off-by: Maximilian Heyne <mheyne@amazon.de>
> > ---
> >
> > I've seen the failures on the 6.18 kernels but haven't tested on latest
> > upstream. However, I still think this is an issue.
> >
> > ---
> > tools/testing/selftests/landlock/audit.h | 13 +++++--------
> > 1 file changed, 5 insertions(+), 8 deletions(-)
> >
> > diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
> > index 834005b2b0f09..7842330875f53 100644
> > --- a/tools/testing/selftests/landlock/audit.h
> > +++ b/tools/testing/selftests/landlock/audit.h
> > @@ -494,10 +494,9 @@ static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
> > static int audit_cleanup(int audit_fd, struct audit_filter *filter)
>
> audit_cleanup() should be called for audit_exec tests too.
>
> > {
> > struct audit_filter new_filter;
> > + int err;
> >
> > if (audit_fd < 0 || !filter) {
> > - int err;
> > -
> > /*
> > * Simulates audit_init_with_exe_filter() when called from
> > * FIXTURE_TEARDOWN_PARENT().
> > @@ -518,12 +517,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter)
> > audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE);
> > audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
> >
> > - /*
> > - * Because audit_cleanup() might not be called by the test auditd
> > - * process, it might not be possible to explicitly set it. Anyway,
> > - * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
> > - * process will exit.
> > - */
>
> Please add a comment that explains that the audit state is not restored
> but just disabled.
>
> > + err = audit_set_status(audit_fd, AUDIT_STATUS_ENABLED, 0);
> > + if (err)
> > + return err;
> > +
> > return close(audit_fd);
>
> FDs should always be closed.
>
> > }
> >
> > --
> > 2.50.1
> >
> >
> >
> >
> > Amazon Web Services Development Center Germany GmbH
> > Tamara-Danz-Str. 13
> > 10243 Berlin
> > Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
> > Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
> > Sitz: Berlin
> > Ust-ID: DE 365 538 597
> >
> >
^ permalink raw reply
* [PATCH] landlock: work around gcc-16 -Wuninitialized warning
From: Arnd Bergmann @ 2026-06-19 8:21 UTC (permalink / raw)
To: Mickaël Salaün, Paul Moore, James Morris,
Serge E. Hallyn, Tingmao Wang, Justin Suess
Cc: Arnd Bergmann, Günther Noack, linux-security-module,
linux-kernel
From: Arnd Bergmann <arnd@arndb.de>
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. This also avoids the extra attributes
for aligning the structure.
Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743
Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
security/landlock/access.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5..89ab9fbcebe4 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -81,7 +81,10 @@ struct layer_mask {
*/
access_mask_t quiet : 1;
#endif /* CONFIG_AUDIT */
-} __packed __aligned(sizeof(access_mask_t));
+ access_mask_t __pad : ((sizeof(access_mask_t) * 8) -
+ LANDLOCK_NUM_ACCESS_MAX -
+ IS_ENABLED(CONFIG_AUDIT));
+};
/*
* Make sure that we don't increase the size of struct layer_mask when storing
--
2.39.5
^ permalink raw reply related
* Re: [PATCH v5 7/8] vfs: Replace security_sb_mount/security_move_mount with granular hooks
From: Song Liu @ 2026-06-19 6:14 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-security-module, linux-fsdevel, selinux, apparmor, paul,
jmorris, serge, viro, jack, john.johansen, stephen.smalley.work,
omosnace, mic, gnoack, takedakn, penguin-kernel, herton,
kernel-team
In-Reply-To: <178179134209.111814.12159888808546010170.b4-reply@b4>
On Thu, Jun 18, 2026 at 10:04 PM Christian Brauner <brauner@kernel.org> wrote:
>
> On 2026-06-18 18:56:42+08:00, Song Liu wrote:
> > On Wed, Jun 17, 2026 at 9:53 PM Christian Brauner <brauner@kernel.org> wrote:
> >
> > > On Thu, May 28, 2026 at 11:26:06AM -0700, Song Liu wrote:
> >
> > [...]
> >
> > > >
> > >
> > > This again is racy as it is called outside of the namespace semaphore:
> > >
> > > err = security_mount_bind(&old_path, path, recurse);
> > > if (err)
> > > return err;
> > >
> > > if (mnt_ns_loop(old_path.dentry))
> > > return -EINVAL;
> > >
> > > LOCK_MOUNT(mp, path);
> > > if (IS_ERR(mp.parent))
> > > return PTR_ERR(mp.parent);
> > >
> > > After LOCK_MOUNT @path might point to a completely different mount then
> > > the one you performed your security checks on.
> >
> > I thought we agreed at LSF/MM/BPF 2026 to add the LSM hooks
> > before taking namespace semaphore, so that it is possible for LSMs
> > to defend against DoS attacks on namespace semaphore? Did I
> > miss/misunderstand something?
>
> I think there was a misunderstanding. What I pointed out was that it's a
> trade-off. If we do call security hooks under the namespace semaphore or
> mount lock than anything that's called under there must take care to not
> cause deadlocks - which is especially easy to do with mount lock and
> even with the namespace semaphore it may get hairy (automounts etc). The
> dos thing is another worry but if an LSM does stupid things we tell it
> to not do stupid things and to go away.
>
> But as the hooks are done right now they are meaningless from a security
> perspective. You might have a policy that allows mounting on dentry_a
> and deny mounting on dentry_b: before LOCK_MOUNT*() you may see dentry_a
> and allow the mount but after LOCK_MOUNT*() someone raced you and shoved
> a dentry_b mount onto dentry_b and now you allow overmounting dentry_b
> which your policy didn't allow -> hosed.
So the direction here is to move security_mount_bind() after
LOCK_MOUNT(mp, path)? This should be easy to fix.
> > > Placement of this hook suffers from the same issue as the bind mount
> > > hook. Here it's worse because the security layer isn't even informed
> > > about MOVE_MOUNT_BENEATH which completely alters the mount relationship.
> >
> > Current hook security_move_mount doesn't handle
> > MOVE_MOUNT_BENEATH. But we can add mflags to security_mount_move().
> > Do we need anything other than mflags?
>
> I think you either need to pass three mounts (source, target, top_mnt)
> where for non-mount beneath target == top_mnt or you need two separate
> hooks. Because for MOVE_MOUNT_BENEATH you may want to have a tri-part
> policy: source, target, top_mnt.
One hook with (source, target, top_mnt) seems easier here. But let me take a
closer look at this.
Thanks,
Song
^ permalink raw reply
* Re: [RFC PATCH 1/2] landlock: fix TCP Fast Open connection bypass
From: Bryam Vargas @ 2026-06-19 1:39 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Mickaël Salaün, Günther Noack, Mikhail Ivanov,
Paul Moore, Eric Dumazet, Neal Cardwell, linux-security-module,
netdev, linux-kernel
In-Reply-To: <e0c7b502-931e-481e-89b0-b47687d2b942@buffet.re>
Thanks, that settles it: MPTCP is out of scope by design, not a gap.
I read 854277e2cc8c ("landlock: Fix non-TCP sockets restriction"). It
changed the sock->type != SOCK_STREAM test to !sk_is_tcp(sock->sk),
dropping SMC/MPTCP/SCTP from the TCP rights on purpose, and 3d4033985ff5
pins that with a "MPTCP actions are not restricted" selftest. So my
"|| sk_protocol == IPPROTO_MPTCP" suggestion was wrong: it would revert
that decision and break the selftest. Please disregard it.
That leaves the series complete as-is on this axis. Keeping both the v0
guard and the 2/2 selftest sk_is_tcp()-only is correct, and the
Tested-by stands for the TCP and IPv6 fast-open path the patch fixes.
Bryam
^ permalink raw reply
* AppArmor: TCP Fast Open bypasses connect mediation (last unaddressed LSM)
From: Bryam Vargas @ 2026-06-19 1:11 UTC (permalink / raw)
To: John Johansen, linux-security-module, apparmor
Cc: Paul Moore, James Morris, Serge E . Hallyn, Mickael Salaun,
Stephen Smalley, Matthieu Buffet, Mikhail Ivanov, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
Hello John, and LSM folks,
I have been working on the Landlock TCP Fast Open connect bypass [1]. Stephen
Smalley's SELinux fix for the same issue [3] -- "Similar to Landlock, SELinux was
not updated when TCP Fast Open support was introduced ..." -- made me go back and
check the rest of the connect-mediating LSMs, since I had only been looking at
Landlock. With Landlock [2], SELinux [3], and now TOMOYO [4] all getting fixes,
AppArmor is the last one with the same gap and no fix yet.
Root cause (shared with the others)
-----------------------------------
security_socket_connect() has a single call site, net/socket.c (the connect(2)
syscall). TCP Fast Open performs an implicit connect inside sendmsg:
tcp_sendmsg -> tcp_sendmsg_fastopen -> __inet_stream_connect(..., is_sendmsg=1)
-> sk->sk_prot->connect() net/ipv4/{tcp.c,af_inet.c}
This never calls security_socket_connect(); the only LSM hook on the path is
security_socket_sendmsg(). mptcp_sendmsg_fastopen reaches the same code and is a
second producer.
AppArmor
--------
apparmor_socket_connect() requests AA_MAY_CONNECT; apparmor_socket_sendmsg() (via
aa_sock_msg_perm) requests AA_MAY_SEND. These are distinct bits, and apparmor_parser
compiles them independently: "network send inet stream," yields accept mask 0x02
while "network connect inet stream," yields 0x40. So an egress-restriction profile
that grants send but not connect is bypassed by MSG_FASTOPEN.
Reproduced on 6.12.88 with apparmor active. Under a profile granting the inet/inet6
stream lifecycle except connect:
aa-exec -p egress_restricted -- ./probe
[TCP ] connect(2)=EACCES(blocked) sendto(MSG_FASTOPEN)=OK(reached) => connection established
[TCP6] connect(2)=EACCES(blocked) sendto(MSG_FASTOPEN)=OK(reached) => connection established
(The coarse "network inet stream," idiom grants connect anyway, so this only bites the
fine-grained "allow send, deny connect" policy that the asymmetry is meant to serve.)
Fix
---
Same shape as the TOMOYO [4] and SELinux [3] fixes: in apparmor_socket_sendmsg (or
aa_sock_msg_perm), when MSG_FASTOPEN is set and msg_name carries a destination on a
not-yet-connected stream socket, additionally require aa_sk_perm(OP_CONNECT,
AA_MAY_CONNECT, sk). I am happy to send that patch and the reproducer.
(A single core check in __inet_stream_connect(), gated on is_sendmsg, would have
covered all five LSMs and both the TCP and MPTCP producers in one place -- the kernel
already mediates the analogous implicit-connect-on-send for AF_UNIX via
security_unix_may_send and for SCTP via security_sctp_bind_connect. But since the
other four LSMs are taking per-hook fixes, AppArmor matching them is the consistent
move; mentioning the core option only in case it is preferred.)
[1] Landlock: LANDLOCK_ACCESS_NET_CONNECT_TCP bypass via TCP Fast Open (report)
https://lore.kernel.org/r/20260616201615.275032-1-hexlabsecurity@proton.me
[2] landlock: fix TCP Fast Open connection bypass (Matthieu Buffet)
https://lore.kernel.org/r/20260617180526.15627-2-matthieu@buffet.re
[3] selinux: check connect-related permissions on TCP Fast Open (Stephen Smalley)
https://lore.kernel.org/r/20260618175513.112443-2-stephen.smalley.work@gmail.com
[4] tomoyo: Enforce connect policy in TCP Fast Open (Matthieu Buffet)
https://lore.kernel.org/r/20260619002207.61104-1-matthieu@buffet.re
Thanks,
Bryam Vargas
^ permalink raw reply
* Re: [RFC PATCH 1/2] landlock: fix TCP Fast Open connection bypass
From: Matthieu Buffet @ 2026-06-19 0:34 UTC (permalink / raw)
To: Bryam Vargas
Cc: Mickaël Salaün, Günther Noack, Mikhail Ivanov,
Paul Moore, Eric Dumazet, Neal Cardwell, linux-security-module,
netdev, linux-kernel
In-Reply-To: <20260618012527.34964-1-hexlabsecurity@proton.me>
Hi Bryam,
On 6/18/2026 3:25 AM, Bryam Vargas wrote:
> One scope note, since you mention MPTCP: an MPTCP socket isn't covered.
> sk_is_tcp() is false for the mptcp parent (sk_protocol is IPPROTO_MPTCP), so
> neither the new sendmsg hook nor the existing socket_connect one mediates it. On
> the patched kernel my MPTCP arm still reaches the blocked port via both connect()
> and MSG_FASTOPEN. If MPTCP is meant to be in scope for CONNECT_TCP, the guard
> wants `|| sk->sk_protocol == IPPROTO_MPTCP` (not sk_is_mptcp(), which is the
> subflow flag).
Indeed, the patch does not try to filter MPTCP: it is not meant to be in
the scope of LANDLOCK_ACCESS_NET_*_TCP rights.
It used to be, but it was a bug, see:
https://lore.kernel.org/all/20250205093651.1424339-2-ivanov.mikhail1@huawei-partners.com/
Have a nice day!
--
Matthieu
^ permalink raw reply
* [PATCH] tomoyo: Enforce connect policy in TCP Fast Open
From: Matthieu Buffet @ 2026-06-19 0:22 UTC (permalink / raw)
To: Kentaro Takeda, Tetsuo Handa
Cc: Bryam Vargas, Mickaël Salaün, Günther Noack,
linux-security-module, Mikhail Ivanov, Paul Moore, Yuchung Cheng,
Eric Dumazet, netdev, Matthieu Buffet
Tomoyo restricted TCP connections in 2011 in commit
059d84dbb389 ("TOMOYO: Add socket operation restriction support.")
using the socket_connect() LSM hook.
However, the MSG_FASTOPEN sendmsg() flag was added in 2012 to allow
combining connect() and the first sendmsg(). Tomoyo was not updated to
take this into account in its send hook.
This resulted in a TCP connect policy bypass similar to that reported in
Landlock in 2024 (see Link below), with the difference that Tomoyo was
fine when originally merged, and the problem got introduced when adding
fastopen support, possibly due to lack of synchronization between lsm
and netdev worlds.
Add MSG_FASTOPEN handling in Tomoyo's existing send hook.
Link: https://github.com/landlock-lsm/linux/issues/41
Link: https://lore.kernel.org/all/20260616201615.275032-1-hexlabsecurity@proton.me/
Fixes: cf60af03ca4e ("net-tcp: Fast Open client - sendmsg(MSG_FASTOPEN)")
Cc: stable@kernel.org
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
security/tomoyo/network.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c
index cfc2a019de1e..7d9ba7268dc2 100644
--- a/security/tomoyo/network.c
+++ b/security/tomoyo/network.c
@@ -764,11 +764,25 @@ int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg,
struct tomoyo_addr_info address;
const u8 family = tomoyo_sock_family(sock->sk);
const unsigned int type = sock->type;
+ int ret;
+ address.protocol = type;
+
+ if ((msg->msg_flags & MSG_FASTOPEN) != 0 && msg->msg_name != NULL &&
+ (sk_is_tcp(sock->sk) ||
+ (sk_is_inet(sock->sk) && type == SOCK_STREAM &&
+ sock->sk->sk_protocol == IPPROTO_MPTCP))) {
+ address.operation = TOMOYO_NETWORK_CONNECT;
+ ret = tomoyo_check_inet_address(
+ (struct sockaddr *)msg->msg_name, msg->msg_namelen,
+ sock->sk->sk_protocol, &address);
+ if (ret != 0)
+ return ret;
+ }
if (!msg->msg_name || !family ||
(type != SOCK_DGRAM && type != SOCK_RAW))
return 0;
- address.protocol = type;
+
address.operation = TOMOYO_NETWORK_SEND;
if (family == PF_UNIX)
return tomoyo_check_unix_address((struct sockaddr *)
--
2.47.3
^ permalink raw reply related
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: David Windsor @ 2026-06-18 22:50 UTC (permalink / raw)
To: bot+bpf-ci
Cc: viro, brauner, jack, ast, daniel, john.fastabend, andrii, eddyz87,
memxor, martin.lau, song, yonghong.song, jolsa, emil, kpsingh,
mattbobrowski, paul, jmorris, serge, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, stephen.smalley.work, omosnace,
casey, shuah, linux-kernel, linux-fsdevel, bpf,
linux-security-module, linux-integrity, selinux, linux-kselftest,
martin.lau, clm, ihor.solodrai
In-Reply-To: <e9ec622a8a49be299cbbfd459e2fc10693bcd65c6dbf00c9e2645281564e5579@mail.kernel.org>
On Thu, Jun 18, 2026 at 5:22 PM <bot+bpf-ci@kernel.org> wrote:
>
> [ ... ]
> >
> > +struct xattr_ctx {
> > + struct xattr *xattrs;
> > + int *xattr_count;
> > +};
> > +
>
> Paul Moore suggested in v2 renaming this to 'struct lsm_xattrs' to avoid
> overloading the term 'ctx' in the LSM space. The struct was renamed from
> 'lsm_xattr_ctx' to 'xattr_ctx' in v3, but 'ctx' is still in the name. Was
> there a reason to keep 'ctx' in the name, or would renaming to 'lsm_xattrs'
> as Paul suggested be preferable?
>
Indeed he did; I mistakenly inverted his original request.
Will send a fix for this, as well as converting GFP_KERNEL to GFP_NOFS
in the allocation in __bpf_init_inode_xattr.
> > /* Default (no) options for the capable function */
> > #define CAP_OPT_NONE 0x0
> > /* If capable should audit the security request */
>
> [ ... ]
>
> > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> > index 564071a92d7d..86a8e188b900 100644
> > --- a/kernel/bpf/bpf_lsm.c
> > +++ b/kernel/bpf/bpf_lsm.c
> > @@ -113,6 +113,9 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog,
> > }
> > #endif
> >
> > +BTF_ID_LIST_SINGLE(bpf_lsm_inode_init_security_btf_ids, func,
> > + bpf_lsm_inode_init_security)
> > +
> > int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
> > const struct bpf_prog *prog)
> > {
> > @@ -137,6 +140,12 @@ int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
> > return -EINVAL;
> > }
> >
> > + /* bpf reserves a fixed number of xattr slots for itself.
> > + * Set the attach limit so the trampoline rejects excess attaches.
> > + */
>
> This isn't a bug, but a subsystem pattern flags this as potentially
> concerning: the BPF subsystem comment-style rule for files under kernel/bpf/
> requires multi-line comments to have the opening /* on its own line, with
> comment text starting on the next line. The preferred form would be:
>
> /*
> * bpf reserves a fixed number of xattr slots for itself.
> * Set the attach limit so the trampoline rejects excess attaches.
> */
>
> > + if (btf_id == bpf_lsm_inode_init_security_btf_ids[0])
> > + prog->aux->attach_limit = BPF_LSM_INODE_INIT_XATTRS;
> > +
> > return 0;
> > }
> >
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27788616397
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox