* [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT
@ 2025-11-20 22:23 Justin Suess
2025-11-20 22:23 ` [PATCH 1/6] landlock: " Justin Suess
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Hi,
This series introduces a new Landlock rule flag,
LANDLOCK_ADD_RULE_NO_INHERIT, which disables inheriting access rights
from parent objects within the same Landlock layer.
This is useful for policies where a parent directory requires broader
access than its children. For example, a sandbox may allow read-write
access to /home/user but prohibit writes to ~/.bashrc or ~/.ssh even
though they are nested inside the parent directory. Today, this is not
possible because access rights always propagate from parent to child
within a layer.
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 change the direct parent subtree of such an object
(rename, rmdir, link) are denied up to the mountpoint.
These parent-directory restrictions mitigate sandbox-restart attacks.
Without them, a sandboxed program could move a protected directory
before exit, causing the next sandbox instance to apply policies on the
wrong path. Denying such operations prevents these attacks.
This v2 series differs from v1 in the following ways:
1. Adds four new selftests for the new flag.
2. Adds a KUnit test for access layer propagation with flags.
2. Extends parent-directory protections (rename/unlink/link) to prevent
sandbox-restart attacks.
3. Rebased on v4 of Tingmao Wang's amazing "quiet flag" series.
For convenience, the original v1 is here:
https://lore.kernel.org/linux-security-module/20251105180019.1432367-1-utilityemal77@gmail.com/T/#t
And the quiet-flag v4 series is here:
https://lore.kernel.org/linux-security-module/cover.1763330228.git.m@maowtm.org/T/#t
A short usage example:
# LL_FS_RO="" LL_FS_RW="/" LL_FS_RO_NO_INHERIT="/a/b/c" landlock-sandboxer sh
# touch /a/b/c/fi # denied (does not inherit / RW)
# rmdir /a/b/c # denied due to parent-directory protections
# mv /a /bad # denied
# mkdir /a/good; touch /a/good/fi # allowed (unrelated to /a/b/c)
All kunit and selftests related to landlock, including the new ones, pass on my test system.
Feedback is very welcome. Thank you for your time and consideration.
Regards,
Justin Suess
Justin Suess (6):
landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api
samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to
landlock-sandboxer
selftests/landlock: Implement selftests for
LANDLOCK_ADD_RULE_NO_INHERIT
landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is
disabled.
landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT
include/uapi/linux/landlock.h | 9 +
samples/landlock/sandboxer.c | 39 +-
security/landlock/audit.h | 3 +-
security/landlock/fs.c | 433 ++++++++++++++++++++-
security/landlock/net.h | 2 +-
security/landlock/object.h | 2 +
security/landlock/ruleset.c | 171 +++++++-
security/landlock/ruleset.h | 39 ++
security/landlock/syscalls.c | 5 +-
tools/testing/selftests/landlock/fs_test.c | 222 ++++++++++-
10 files changed, 883 insertions(+), 42 deletions(-)
base-commit: d74278e895485ffe1c3708f7f2bc5c5b364aab0a
--
2.51.2
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
2025-11-20 22:23 ` [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api Justin Suess
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Implements a flag to prevent access grant inheritance within the filesystem hierarchy
for landlock rules.
If a landlock rule on an inode has this flag, any access grants on parent inodes will be
ignored. Moreover, operations that involve altering the direct parent tree of the subject with
LANDLOCK_ADD_RULE_NO_INHERIT will be denied up to the mountpoint.
For example, if /a/b/c/ = read only + LANDLOCK_ADD_RULE_NO_INHERIT and / = read write, writes to
files in /a/b/c will be denied. Moreover, moving /a to /bad, removing /a/b/c, or creating links to
/a will be prohibited.
The parent directory restrictions mitigate sandbox-restart attacks. For example, if a sandboxed program
is able to move a LANDLOCK_ADD_RULE_NO_INHERIT restricted directory, upon sandbox restart, the policy
applied naively on the same filenames would be invalid. Preventing these operations mitigates these attacks.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/fs.c | 431 ++++++++++++++++++++++++++++++++++--
security/landlock/object.h | 2 +
security/landlock/ruleset.c | 84 ++++++-
security/landlock/ruleset.h | 39 ++++
4 files changed, 539 insertions(+), 17 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 5fb2c9810e6b..ebeee080ea7a 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -14,6 +14,7 @@
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
+#include <linux/compiler.h>
#include <linux/compiler_types.h>
#include <linux/dcache.h>
#include <linux/err.h>
@@ -23,6 +24,7 @@
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <linux/mount.h>
@@ -36,6 +38,7 @@
#include <linux/types.h>
#include <linux/wait_bit.h>
#include <linux/workqueue.h>
+#include <linux/xarray.h>
#include <uapi/linux/fiemap.h>
#include <uapi/linux/landlock.h>
@@ -317,21 +320,152 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
LANDLOCK_ACCESS_FS_IOCTL_DEV)
/* clang-format on */
+static inline layer_mask_t landlock_layer_bit(
+ const struct landlock_layer *layer, u32 layer_index)
+{
+ u32 level = layer->level;
+
+ if (!level)
+ level = layer_index + 1;
+ return BIT_ULL(level - 1);
+}
+
+static const struct landlock_rule *find_rule(
+ const struct landlock_ruleset *const domain,
+ const struct dentry *const dentry);
+
+static layer_mask_t get_ruleset_cached_no_inherit_layers(
+ const struct landlock_ruleset *const ruleset,
+ struct landlock_object *object)
+{
+ struct landlock_no_inherit_desc *desc;
+
+ if (!ruleset || !object)
+ return 0;
+
+ /* xa_load doesn't accept const, but we're only reading */
+ desc = xa_load(&((struct landlock_ruleset *)ruleset)->no_inherit_desc,
+ (unsigned long)object);
+ if (!desc)
+ return 0;
+ return desc->desc_layers;
+}
+
+static layer_mask_t get_no_inherit_desc_layers_for_dentry(
+ const struct landlock_ruleset *const ruleset,
+ struct dentry *const dentry)
+{
+ const struct landlock_rule *rule;
+ struct landlock_object *object;
+ layer_mask_t layers = 0;
+ u32 layer_index;
+
+ if (!ruleset || !dentry || d_is_negative(dentry))
+ return 0;
+
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(d_backing_inode(dentry))->object);
+ if (object)
+ layers |= get_ruleset_cached_no_inherit_layers(ruleset, object);
+ rcu_read_unlock();
+
+ rule = find_rule(ruleset, dentry);
+ if (!rule)
+ return layers;
+
+ for (layer_index = 0; layer_index < rule->num_layers; layer_index++) {
+ const struct landlock_layer *layer = &rule->layers[layer_index];
+
+ if (!layer->flags.no_inherit &&
+ !layer->flags.has_no_inherit_descendant)
+ continue;
+ layers |= landlock_layer_bit(layer, layer_index);
+ }
+
+ return layers;
+}
+
+static void mark_no_inherit_ancestors(struct landlock_ruleset *ruleset,
+ struct dentry *dentry,
+ layer_mask_t descendant_layers);
+
+static bool mask_no_inherit_descendant_layers(
+ const struct landlock_ruleset *const domain,
+ struct dentry *const dentry,
+ layer_mask_t child_layers,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
+ struct collected_rule_flags *const rule_flags)
+{
+ layer_mask_t descendant_layers;
+ const unsigned long access_req = access_request;
+ unsigned long access_bit;
+ bool changed = false;
+
+ if (!access_request || !layer_masks || !rule_flags || !dentry)
+ return false;
+ if (d_is_negative(dentry))
+ return false;
+
+ descendant_layers = get_no_inherit_desc_layers_for_dentry(domain, dentry);
+ {
+ layer_mask_t shared_layers = descendant_layers & child_layers;
+
+ if (shared_layers) {
+ rule_flags->no_inherit_masks |= shared_layers;
+ rule_flags->no_inherit_desc_masks |= shared_layers;
+ }
+ }
+ descendant_layers &= ~child_layers;
+ descendant_layers &= ~rule_flags->no_inherit_masks;
+ if (!descendant_layers)
+ return false;
+
+ for_each_set_bit(access_bit, &access_req,
+ ARRAY_SIZE(*layer_masks)) {
+ layer_mask_t *const layer_mask = &(*layer_masks)[access_bit];
+
+ if (*layer_mask & descendant_layers) {
+ *layer_mask &= ~descendant_layers;
+ changed = true;
+ }
+ }
+
+ if (!changed)
+ return false;
+
+ rule_flags->no_inherit_masks |= descendant_layers;
+ rule_flags->no_inherit_desc_masks |= descendant_layers;
+
+ return true;
+}
+
/*
* @path: Should have been checked by get_path_from_fd().
*/
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
- const struct path *const path,
- access_mask_t access_rights, const int flags)
+ const struct path *const path,
+ access_mask_t access_rights, const int flags)
{
int err;
+ const bool is_dir = d_is_dir(path->dentry);
struct landlock_id id = {
.type = LANDLOCK_KEY_INODE,
};
/* Files only get access rights that make sense. */
- if (!d_is_dir(path->dentry) &&
- (access_rights | ACCESS_FILE) != ACCESS_FILE)
+ if (!is_dir) {
+ const access_mask_t file_rights = access_rights & ACCESS_FILE;
+
+ /*
+ * Reject purely directory-only access on files, but allow
+ * metadata-only quiet rules.
+ */
+ if (!file_rights && !(flags & LANDLOCK_ADD_RULE_QUIET))
+ return -EINVAL;
+ access_rights = file_rights;
+ }
+ if ((flags & LANDLOCK_ADD_RULE_NO_INHERIT) && !is_dir)
return -EINVAL;
if (WARN_ON_ONCE(ruleset->num_layers != 1))
return -EINVAL;
@@ -344,6 +478,39 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights, flags);
+ if (!err && (flags & LANDLOCK_ADD_RULE_NO_INHERIT)) {
+ const struct landlock_rule *rule;
+ layer_mask_t descendant_layers = 0;
+ u32 layer_index;
+
+ rule = find_rule(ruleset, path->dentry);
+ if (rule) {
+ for (layer_index = 0; layer_index < rule->num_layers; layer_index++) {
+ const struct landlock_layer *layer =
+ &rule->layers[layer_index];
+
+ if (layer->flags.no_inherit ||
+ layer->flags.has_no_inherit_descendant)
+ descendant_layers |=
+ landlock_layer_bit(layer, layer_index);
+ }
+ if (descendant_layers)
+ mark_no_inherit_ancestors(ruleset, path->dentry,
+ descendant_layers);
+ }
+ }
+
+ /*
+ * Register this object in the xarray even if it doesn't currently have
+ * descendant no-inherit layers, so collect_no_inherit_layers() can walk
+ * ancestors reliably when checking topology changes.
+ */
+ if (!err) {
+ const struct landlock_rule *rule = find_rule(ruleset, path->dentry);
+
+ if (rule)
+ landlock_set_no_inherit_desc_layers(ruleset, id.key.object, 0);
+ }
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
@@ -382,6 +549,102 @@ find_rule(const struct landlock_ruleset *const domain,
return rule;
}
+
+static void mark_no_inherit_ancestors(struct landlock_ruleset *ruleset,
+ struct dentry *dentry,
+ layer_mask_t descendant_layers)
+{
+ struct dentry *cursor;
+ u32 layer_index;
+
+ if (!ruleset || !dentry || !descendant_layers)
+ return;
+
+ lockdep_assert_held(&ruleset->lock);
+
+ cursor = dget(dentry);
+ while (cursor) {
+ struct dentry *parent;
+
+ if (IS_ROOT(cursor)) {
+ dput(cursor);
+ break;
+ }
+
+ parent = dget_parent(cursor);
+ dput(cursor);
+ if (!parent)
+ break;
+
+ if (!d_is_negative(parent)) {
+ const struct landlock_rule *rule;
+ struct landlock_object *parent_object;
+
+ rule = find_rule(ruleset, parent);
+ if (rule) {
+ struct landlock_rule *mutable_rule =
+ (struct landlock_rule *)rule;
+
+ for (layer_index = 0;
+ layer_index < mutable_rule->num_layers;
+ layer_index++) {
+ struct landlock_layer *layer =
+ &mutable_rule->layers[layer_index];
+ layer_mask_t layer_bit =
+ landlock_layer_bit(layer, layer_index);
+
+ if (descendant_layers & layer_bit)
+ layer->flags.has_no_inherit_descendant = true;
+ }
+ }
+
+ parent_object = get_inode_object(d_backing_inode(parent));
+ if (!IS_ERR(parent_object)) {
+ landlock_set_no_inherit_desc_layers(ruleset, parent_object,
+ descendant_layers);
+ landlock_put_object(parent_object);
+ }
+ }
+
+ cursor = parent;
+ }
+}
+
+static layer_mask_t get_no_inherit_layers_for_target(
+ const struct landlock_ruleset *const ruleset,
+ struct dentry *const dentry,
+ const bool include_descendants)
+{
+ struct landlock_object *object;
+ const struct landlock_rule *rule;
+ layer_mask_t layers = 0;
+ u32 layer_index;
+
+ if (!ruleset || !dentry || d_is_negative(dentry))
+ return 0;
+
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(d_backing_inode(dentry))->object);
+ if (object && include_descendants)
+ layers |= get_ruleset_cached_no_inherit_layers(ruleset, object);
+ rcu_read_unlock();
+
+ rule = find_rule(ruleset, dentry);
+ if (!rule)
+ return layers;
+
+ for (layer_index = 0; layer_index < rule->num_layers; layer_index++) {
+ const struct landlock_layer *layer = &rule->layers[layer_index];
+
+ if (layer->flags.no_inherit ||
+ (include_descendants &&
+ layer->flags.has_no_inherit_descendant))
+ layers |= landlock_layer_bit(layer, layer_index);
+ }
+
+ return layers;
+}
+
/*
* Allows access to pseudo filesystems that will never be mountable (e.g.
* sockfs, pipefs), but can still be reachable through
@@ -778,6 +1041,13 @@ static bool is_access_to_paths_allowed(
layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL,
(*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL;
struct collected_rule_flags _rule_flag_parent1_bkp, _rule_flag_parent2_bkp;
+ layer_mask_t child1_layers = 0;
+ layer_mask_t child2_layers = 0;
+
+ if (dentry_child1)
+ child1_layers = get_no_inherit_desc_layers_for_dentry(domain, dentry_child1);
+ if (dentry_child2)
+ child2_layers = get_no_inherit_desc_layers_for_dentry(domain, dentry_child2);
if (!access_request_parent1 && !access_request_parent2)
return true;
@@ -976,8 +1246,12 @@ static bool is_access_to_paths_allowed(
memcpy(&_rule_flag_parent2_bkp,
rule_flags_parent2,
sizeof(_rule_flag_parent2_bkp));
- is_dom_check_bkp = is_dom_check;
}
+ is_dom_check_bkp = is_dom_check;
+ child1_layers = get_no_inherit_desc_layers_for_dentry(
+ domain, walker_path.dentry);
+ if (layer_masks_parent2)
+ child2_layers = child1_layers;
/* Ignores hidden mount points. */
goto jump_up;
@@ -1001,15 +1275,48 @@ static bool is_access_to_paths_allowed(
break;
}
- /*
- * We reached a disconnected root directory from a bind mount, and
- * we need to reset the walk to the current mount root.
- */
- goto reset_to_mount_root;
- }
- parent_dentry = dget_parent(walker_path.dentry);
- dput(walker_path.dentry);
- walker_path.dentry = parent_dentry;
+ /*
+ * We reached a disconnected root directory from a bind mount, and
+ * we need to reset the walk to the current mount root.
+ */
+ goto reset_to_mount_root;
+ }
+ if (likely(!d_is_negative(walker_path.dentry))) {
+ child1_layers = get_no_inherit_desc_layers_for_dentry(
+ domain, walker_path.dentry);
+ if (layer_masks_parent2)
+ child2_layers = child1_layers;
+ } else {
+ child1_layers = 0;
+ if (layer_masks_parent2)
+ child2_layers = 0;
+ }
+ parent_dentry = dget_parent(walker_path.dentry);
+ dput(walker_path.dentry);
+ walker_path.dentry = parent_dentry;
+ /*
+ * Apply descendant no-inherit masking now that we've moved to the
+ * parent. This ensures the parent respects any no-inherit rules from
+ * the child we just left. Only applies to refer operations (rename/link).
+ */
+ if (unlikely(layer_masks_parent2)) {
+ if (mask_no_inherit_descendant_layers(
+ domain, walker_path.dentry, child1_layers,
+ access_masked_parent1,
+ layer_masks_parent1, rule_flags_parent1))
+ allowed_parent1 =
+ allowed_parent1 ||
+ is_layer_masks_allowed(layer_masks_parent1);
+
+ if (rule_flags_parent2 &&
+ mask_no_inherit_descendant_layers(
+ domain, walker_path.dentry, child2_layers,
+ access_masked_parent2,
+ layer_masks_parent2, rule_flags_parent2))
+ allowed_parent2 =
+ allowed_parent2 ||
+ is_layer_masks_allowed(layer_masks_parent2);
+ }
continue;
reset_to_mount_root:
@@ -1057,6 +1364,10 @@ static bool is_access_to_paths_allowed(
dput(walker_path.dentry);
walker_path.dentry = walker_path.mnt->mnt_root;
dget(walker_path.dentry);
+ child1_layers = get_no_inherit_desc_layers_for_dentry(
+ domain, walker_path.dentry);
+ if (layer_masks_parent2)
+ child2_layers = child1_layers;
}
path_put(&walker_path);
@@ -1083,7 +1394,7 @@ static bool is_access_to_paths_allowed(
}
static int current_check_access_path(const struct path *const path,
- access_mask_t access_request)
+ access_mask_t access_request)
{
const struct access_masks masks = {
.fs = access_request,
@@ -1102,7 +1413,7 @@ static int current_check_access_path(const struct path *const path,
LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(subject->domain, path, access_request,
&layer_masks, &rule_flags, &request,
- NULL, 0, NULL, NULL, NULL, NULL))
+ NULL, 0, NULL, NULL, NULL, NULL))
return 0;
landlock_log_denial(subject, &request, rule_flags);
@@ -1233,6 +1544,65 @@ static bool collect_domain_accesses(
return ret;
}
+static layer_mask_t collect_no_inherit_layers(
+ const struct landlock_ruleset *domain, struct dentry *dentry)
+{
+ struct dentry *cursor, *parent;
+ layer_mask_t layers = 0;
+ bool include_descendants = true;
+
+ if (!domain || !dentry)
+ return 0;
+
+ cursor = dget(dentry);
+ if (!cursor)
+ return 0;
+
+ while (true) {
+ layers = get_no_inherit_layers_for_target(domain, cursor,
+ include_descendants);
+ if (layers || IS_ROOT(cursor) || d_is_negative(cursor))
+ break;
+
+ parent = dget_parent(cursor);
+ dput(cursor);
+ if (!parent)
+ return layers;
+ cursor = parent;
+ include_descendants = false;
+ }
+
+ dput(cursor);
+ return layers;
+}
+
+static int deny_no_inherit_topology_change(
+ const struct landlock_cred_security *subject,
+ struct dentry *dentry)
+{
+ layer_mask_t sealed_layers;
+ unsigned long layer_index;
+
+ if (!subject || !dentry || d_is_negative(dentry))
+ return 0;
+
+ sealed_layers = collect_no_inherit_layers(subject->domain, dentry);
+ if (!sealed_layers)
+ return 0;
+
+ layer_index = __ffs((unsigned long)sealed_layers);
+ 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 = layer_index + 1,
+ }, no_rule_flags);
+
+ return -EACCES;
+}
+
/**
* current_check_refer_path - Check if a rename or link action is allowed
*
@@ -1319,6 +1689,16 @@ static int current_check_refer_path(struct dentry *const old_dentry,
access_request_parent2 =
get_mode_access(d_backing_inode(old_dentry)->i_mode);
if (removable) {
+ int err;
+
+ err = deny_no_inherit_topology_change(subject, old_dentry);
+ if (err)
+ return err;
+ if (exchange) {
+ err = deny_no_inherit_topology_change(subject, new_dentry);
+ if (err)
+ return err;
+ }
access_request_parent1 |= maybe_remove(old_dentry);
access_request_parent2 |= maybe_remove(new_dentry);
}
@@ -1711,12 +2091,31 @@ static int hook_path_symlink(const struct path *const dir,
static int hook_path_unlink(const struct path *const dir,
struct dentry *const dentry)
{
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs, NULL);
+ int err;
+
+ if (subject) {
+ err = deny_no_inherit_topology_change(subject, dentry);
+ if (err)
+ return err;
+ }
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
}
static int hook_path_rmdir(const struct path *const dir,
struct dentry *const dentry)
{
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs, NULL);
+ int err;
+
+ if (subject) {
+ err = deny_no_inherit_topology_change(subject, dentry);
+ if (err)
+ return err;
+ }
+
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
}
diff --git a/security/landlock/object.h b/security/landlock/object.h
index 5f28c35e8aa8..2520955825bb 100644
--- a/security/landlock/object.h
+++ b/security/landlock/object.h
@@ -13,6 +13,8 @@
#include <linux/refcount.h>
#include <linux/spinlock.h>
+#include "access.h"
+
struct landlock_object;
/**
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index fd633aee0599..63aa420ab593 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -48,6 +48,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
#endif /* IS_ENABLED(CONFIG_INET) */
new_ruleset->num_layers = num_layers;
+ xa_init(&new_ruleset->no_inherit_desc);
/*
* hierarchy = NULL
* num_rules = 0
@@ -108,6 +109,45 @@ static bool is_object_pointer(const enum landlock_key_type key_type)
}
}
+void landlock_set_no_inherit_desc_layers(struct landlock_ruleset *ruleset,
+ struct landlock_object *object,
+ layer_mask_t layers)
+{
+ struct landlock_no_inherit_desc *desc;
+ int err;
+
+ if (!ruleset || !object || !layers)
+ return;
+
+ desc = xa_load(&ruleset->no_inherit_desc, (unsigned long)object);
+ if (desc) {
+ desc->desc_layers |= layers;
+ return;
+ }
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL_ACCOUNT);
+ if (!desc)
+ return;
+
+ desc->object = object;
+ desc->desc_layers = layers;
+ landlock_get_object(object);
+ err = xa_insert(&ruleset->no_inherit_desc, (unsigned long)object, desc,
+ GFP_KERNEL_ACCOUNT);
+ if (err) {
+ struct landlock_no_inherit_desc *existing;
+
+ if (err == -EBUSY) {
+ existing = xa_load(&ruleset->no_inherit_desc,
+ (unsigned long)object);
+ if (existing)
+ existing->desc_layers |= layers;
+ }
+ landlock_put_object(object);
+ kfree(desc);
+ }
+}
+
static struct landlock_rule *
create_rule(const struct landlock_id id,
const struct landlock_layer (*const layers)[], const u32 num_layers,
@@ -257,6 +297,10 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
return -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 0;
}
@@ -315,7 +359,10 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
.level = 0,
.flags = {
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
- },
+ .no_inherit = !!(flags & LANDLOCK_ADD_RULE_NO_INHERIT),
+ .has_no_inherit_descendant =
+ !!(flags & LANDLOCK_ADD_RULE_NO_INHERIT),
+ }
} };
build_check_layer();
@@ -402,6 +449,15 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
goto out_unlock;
#endif /* IS_ENABLED(CONFIG_INET) */
+ {
+ unsigned long index;
+ struct landlock_no_inherit_desc *desc;
+
+ xa_for_each(&src->no_inherit_desc, index, desc)
+ landlock_set_no_inherit_desc_layers(dst, desc->object,
+ desc->desc_layers);
+ }
+
out_unlock:
mutex_unlock(&src->lock);
mutex_unlock(&dst->lock);
@@ -465,6 +521,15 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
goto out_unlock;
#endif /* IS_ENABLED(CONFIG_INET) */
+ {
+ unsigned long index;
+ struct landlock_no_inherit_desc *desc;
+
+ xa_for_each(&parent->no_inherit_desc, index, desc)
+ landlock_set_no_inherit_desc_layers(child, desc->object,
+ desc->desc_layers);
+ }
+
if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
err = -EINVAL;
goto out_unlock;
@@ -489,6 +554,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
static void free_ruleset(struct landlock_ruleset *const ruleset)
{
struct landlock_rule *freeme, *next;
+ struct landlock_no_inherit_desc *desc;
+ unsigned long index;
might_sleep();
rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
@@ -501,6 +568,12 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
free_rule(freeme, LANDLOCK_KEY_NET_PORT);
#endif /* IS_ENABLED(CONFIG_INET) */
+ xa_for_each(&ruleset->no_inherit_desc, index, desc) {
+ landlock_put_object(desc->object);
+ kfree(desc);
+ }
+ xa_destroy(&ruleset->no_inherit_desc);
+
landlock_put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
@@ -660,9 +733,18 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
unsigned long access_bit;
bool is_empty;
+ /* Skip layers that already have no inherit flags. */
+ if (rule_flags &&
+ (rule_flags->no_inherit_masks & layer_bit))
+ continue;
+
/* Collect rule flags for each layer. */
if (rule_flags && layer->flags.quiet)
rule_flags->quiet_masks |= layer_bit;
+ if (rule_flags && layer->flags.no_inherit)
+ rule_flags->no_inherit_masks |= layer_bit;
+ if (rule_flags && layer->flags.has_no_inherit_descendant)
+ rule_flags->no_inherit_desc_masks |= layer_bit;
/*
* Records in @layer_masks which layer grants access to each requested
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index cd0434d8dc63..4e0484c1575d 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -15,6 +15,7 @@
#include <linux/rbtree.h>
#include <linux/refcount.h>
#include <linux/workqueue.h>
+#include <linux/xarray.h>
#include "access.h"
#include "limits.h"
@@ -40,6 +41,18 @@ struct landlock_layer {
* down the file hierarchy.
*/
bool quiet:1;
+ /**
+ * @no_inherit: When set, this layer's rule does not inherit
+ * allowed accesses from parent objects within the same layer.
+ * (currently only applies to filesystem objects)
+ */
+ bool no_inherit:1;
+ /**
+ * @has_no_inherit_descendant: Marks that a descendant rule within
+ * this layer carries the no-inherit flag and therefore seals
+ * topology changes on the path.
+ */
+ bool has_no_inherit_descendant:1;
} flags;
/**
* @access: Bitfield of allowed actions on the kernel object. They are
@@ -56,6 +69,15 @@ struct collected_rule_flags {
* @quiet_masks: Layers for which the quiet flag is effective.
*/
layer_mask_t quiet_masks;
+ /**
+ * @no_inherit_masks: Layers for which the no_inherit flag is effective.
+ */
+ layer_mask_t no_inherit_masks;
+ /**
+ * @no_inherit_desc_masks: Layers for which a descendant rule carries
+ * the no_inherit flag.
+ */
+ layer_mask_t no_inherit_desc_masks;
};
/**
@@ -136,6 +158,11 @@ struct landlock_rule {
struct landlock_layer layers[] __counted_by(num_layers);
};
+struct landlock_no_inherit_desc {
+ struct landlock_object *object;
+ layer_mask_t desc_layers;
+};
+
/**
* struct landlock_ruleset - Landlock ruleset
*
@@ -190,6 +217,12 @@ struct landlock_ruleset {
* @num_rules: Number of non-overlapping (i.e. not for
* the same object) rules in this ruleset.
*/
+ /**
+ * @no_inherit_desc: XArray containing objects
+ * with no_inherit descendants in this ruleset.
+ * This is used to quickly merge no_inherit flags,
+ */
+ struct xarray no_inherit_desc;
u32 num_rules;
/**
* @num_layers: Number of layers that are used in this
@@ -345,4 +378,10 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
layer_mask_t (*const layer_masks)[],
const enum landlock_key_type key_type);
+
+
+void landlock_set_no_inherit_desc_layers(struct landlock_ruleset *ruleset,
+ struct landlock_object *object,
+ layer_mask_t layers);
+
#endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2025-11-20 22:23 ` [PATCH 1/6] landlock: " Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
2025-11-23 21:03 ` Tingmao Wang
2025-11-20 22:23 ` [PATCH 3/6] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer Justin Suess
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Implements the syscall side flag handling and kernel api headers for the
LANDLOCK_ADD_RULE_NO_INHERIT flag.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
include/uapi/linux/landlock.h | 9 +++++++++
security/landlock/syscalls.c | 5 ++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 50f0806b7e33..d9daef551d96 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -127,10 +127,19 @@ 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
+ * When this flag is set while adding a rule to a ruleset, the rule
+ * will not inherit allowed accesses from rules on parent objects
+ * within the same layer. (currently only applies to filesystem objects)
+ * By default, Landlock rules added to a ruleset inherit allowed accesses
+ * from parent objects, meaning that if a parent directory has been granted
+ * certain access rights, those rights will also apply to its child objects.
+ * This flag prevents such inheritance for the specific rule being added.
*/
/* 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 93396bfc1500..200287a34895 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -463,7 +463,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
if (!is_initialized())
return -EOPNOTSUPP;
- if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
+ if (flags && flags & ~(LANDLOCK_ADD_RULE_QUIET | LANDLOCK_ADD_RULE_NO_INHERIT))
+ return -EINVAL;
+ if ((flags & LANDLOCK_ADD_RULE_NO_INHERIT) &&
+ rule_type != LANDLOCK_RULE_PATH_BENEATH)
return -EINVAL;
/* Gets and checks the ruleset. */
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 3/6] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2025-11-20 22:23 ` [PATCH 1/6] landlock: " Justin Suess
2025-11-20 22:23 ` [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
2025-11-20 22:23 ` [PATCH 4/6] selftests/landlock: Implement selftests for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Adds support to landlock-sandboxer with environment variables LL_FS_RO_NO_INHERIT
and LL_FS_RW_NO_INHERIT. These create the same rulesets as their non-no inherit varients,
plus the LANDLOCK_ADD_RULE_NO_INHERIT flag.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
samples/landlock/sandboxer.c | 39 +++++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 2d8e3e94b77b..2b40b2df83b4 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,6 +58,8 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_FS_RO_NAME "LL_FS_RO"
#define ENV_FS_RW_NAME "LL_FS_RW"
+#define ENV_FS_RO_NO_INHERIT_NAME "LL_FS_RO_NO_INHERIT"
+#define ENV_FS_RW_NO_INHERIT_NAME "LL_FS_RW_NO_INHERIT"
#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
#define ENV_FS_QUIET_ACCESS_NAME "LL_FS_QUIET_ACCESS"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
@@ -121,7 +123,8 @@ static int parse_path(char *env_path, const char ***const path_list)
/* clang-format on */
static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
- const __u64 allowed_access, bool quiet)
+ const __u64 allowed_access,
+ __u32 add_rule_flags, bool mandatory)
{
int num_paths, i, ret = 1;
char *env_path_name;
@@ -132,9 +135,13 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
env_path_name = getenv(env_var);
if (!env_path_name) {
- /* Prevents users to forget a setting. */
- fprintf(stderr, "Missing environment variable %s\n", env_var);
- return 1;
+ if (mandatory) {
+ /* Prevents users to forget a setting. */
+ fprintf(stderr, "Missing environment variable %s\n",
+ env_var);
+ return 1;
+ }
+ return 0;
}
env_path_name = strdup(env_path_name);
unsetenv(env_var);
@@ -171,8 +178,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
if (!S_ISDIR(statbuf.st_mode))
path_beneath.allowed_access &= ACCESS_FILE;
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
- &path_beneath,
- quiet ? LANDLOCK_ADD_RULE_QUIET : 0)) {
+ &path_beneath, add_rule_flags)) {
fprintf(stderr,
"Failed to update the ruleset with \"%s\": %s\n",
path_list[i], strerror(errno));
@@ -375,6 +381,8 @@ static const char help[] =
"Optional settings (when not set, their associated access check "
"is always allowed, which is different from an empty string which "
"means an empty list):\n"
+ "* " ENV_FS_RO_NO_INHERIT_NAME ": read-only paths without rule inheritance\n"
+ "* " ENV_FS_RW_NO_INHERIT_NAME ": read-write paths without rule inheritance\n"
"* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n"
"* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n"
"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
@@ -596,17 +604,28 @@ int main(const int argc, char *const argv[], char *const *const envp)
}
if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro,
- false)) {
+ 0, true)) {
goto err_close_ruleset;
}
if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw,
- false)) {
+ 0, true)) {
+ goto err_close_ruleset;
+ }
+ /* Optional no-inherit rules mirror the regular read-only/read-write sets. */
+ if (populate_ruleset_fs(ENV_FS_RO_NO_INHERIT_NAME, ruleset_fd,
+ access_fs_ro, LANDLOCK_ADD_RULE_NO_INHERIT,
+ false)) {
+ goto err_close_ruleset;
+ }
+ if (populate_ruleset_fs(ENV_FS_RW_NO_INHERIT_NAME, ruleset_fd,
+ access_fs_rw, LANDLOCK_ADD_RULE_NO_INHERIT,
+ false)) {
goto err_close_ruleset;
}
/* Don't require this env to be present. */
- if (quiet_supported && getenv(ENV_FS_QUIET_NAME)) {
+ if (quiet_supported) {
if (populate_ruleset_fs(ENV_FS_QUIET_NAME, ruleset_fd, 0,
- true)) {
+ LANDLOCK_ADD_RULE_QUIET, false)) {
goto err_close_ruleset;
}
}
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 4/6] selftests/landlock: Implement selftests for LANDLOCK_ADD_RULE_NO_INHERIT
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
` (2 preceding siblings ...)
2025-11-20 22:23 ` [PATCH 3/6] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
2025-11-20 22:23 ` [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled Justin Suess
2025-11-20 22:23 ` [PATCH 6/6] landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
5 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Implements 5 selftests for the flag, covering allowed and disallowed operations on parent
and child directories when this flag is set, as well as multi-layer configurations.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
tools/testing/selftests/landlock/fs_test.c | 222 +++++++++++++++++++--
1 file changed, 210 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index d4819ff44230..1cdded3f67e6 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -717,16 +717,12 @@ TEST_F_FORK(layout1, rule_with_unhandled_access)
}
static void add_path_beneath(struct __test_metadata *const _metadata,
- const int ruleset_fd, const __u64 allowed_access,
- const char *const path, bool quiet)
+ const int ruleset_fd, const __u64 allowed_access,
+ const char *const path, __u32 flags)
{
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = allowed_access,
};
- __u32 flags = 0;
-
- if (quiet)
- flags |= LANDLOCK_ADD_RULE_QUIET;
path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC);
ASSERT_LE(0, path_beneath.parent_fd)
@@ -790,7 +786,7 @@ static int create_ruleset(struct __test_metadata *const _metadata,
continue;
add_path_beneath(_metadata, ruleset_fd, rules[i].access,
- rules[i].path, false);
+ rules[i].path, 0);
}
return ruleset_fd;
}
@@ -1368,7 +1364,7 @@ TEST_F_FORK(layout1, inherit_subset)
* ANDed with the previous ones.
*/
add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
- dir_s1d2, false);
+ dir_s1d2, 0);
/*
* According to ruleset_fd, dir_s1d2 should now have the
* LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE
@@ -1400,7 +1396,7 @@ TEST_F_FORK(layout1, inherit_subset)
* Try to get more privileges by adding new access rights to the parent
* directory: dir_s1d1.
*/
- add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, false);
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, 0);
enforce_ruleset(_metadata, ruleset_fd);
/* Same tests and results as above. */
@@ -1423,7 +1419,7 @@ TEST_F_FORK(layout1, inherit_subset)
* that there was no rule tied to it before.
*/
add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
- dir_s1d3, false);
+ dir_s1d3, 0);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(0, close(ruleset_fd));
@@ -1476,7 +1472,7 @@ TEST_F_FORK(layout1, inherit_superset)
add_path_beneath(_metadata, ruleset_fd,
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR,
- dir_s1d2, false);
+ dir_s1d2, 0);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(0, close(ruleset_fd));
@@ -1488,6 +1484,39 @@ TEST_F_FORK(layout1, inherit_superset)
ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
}
+TEST_F_FORK(layout1, inherit_no_inherit_flag)
+{
+ struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = ACCESS_RW,
+ };
+ int ruleset_fd;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, 0);
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d2,
+ LANDLOCK_ADD_RULE_NO_INHERIT);
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ /* Parent directory still grants write access to its direct children. */
+ EXPECT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
+ EXPECT_EQ(0, test_open(file1_s1d1, O_WRONLY));
+
+ /* dir_s1d2 gets only its explicit read-only access rights. */
+ EXPECT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
+ EXPECT_EQ(0, test_open(file1_s1d2, O_RDONLY));
+ EXPECT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
+
+ /* Descendants of dir_s1d2 inherit the reduced access mask. */
+ EXPECT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY));
+ EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
+ EXPECT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY));
+}
+
TEST_F_FORK(layout0, max_layers)
{
int i, err;
@@ -4412,6 +4441,175 @@ TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
ASSERT_EQ(0, close(cli_fd));
}
+TEST_F_FORK(layout1, inherit_no_inherit_topology_dir)
+{
+ const struct rule rules[] = {
+ {
+ .path = TMP_DIR,
+ .access = ACCESS_RW,
+ },
+ {},
+ };
+ int ruleset_fd;
+
+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Adds a no-inherit rule on a leaf directory. */
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d3,
+ LANDLOCK_ADD_RULE_NO_INHERIT);
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ /*
+ * Topology modifications of the rule path and its parents are denied.
+ */
+
+ /* Target directory s1d3 */
+ ASSERT_EQ(-1, rmdir(dir_s1d3));
+ ASSERT_EQ(EACCES, errno);
+ ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3));
+ ASSERT_EQ(EACCES, errno);
+
+ /* Parent directory s1d2 */
+ ASSERT_EQ(-1, rmdir(dir_s1d2));
+ ASSERT_EQ(EACCES, errno);
+ ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2));
+ ASSERT_EQ(EACCES, errno);
+
+ /* Grandparent directory s1d1 */
+ ASSERT_EQ(-1, rmdir(dir_s1d1));
+ ASSERT_EQ(EACCES, errno);
+ ASSERT_EQ(-1, rename(dir_s1d1, dir_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * Sibling operations are allowed.
+ */
+ /* Sibling of s1d3 */
+ ASSERT_EQ(0, unlink(file1_s1d2));
+ /* Sibling of s1d2 */
+ ASSERT_EQ(0, unlink(file1_s1d1));
+
+ /*
+ * Content of the no-inherit directory is restricted by the rule (RO).
+ */
+ ASSERT_EQ(-1, unlink(file1_s1d3));
+ ASSERT_EQ(EACCES, errno);
+}
+
+TEST_F_FORK(layout1, inherit_no_inherit_topology_unrelated)
+{
+ const struct rule rules[] = {
+ {
+ .path = TMP_DIR,
+ .access = ACCESS_RW,
+ },
+ {},
+ };
+ static const char unrelated_dir[] = TMP_DIR "/s2d1/unrelated";
+ static const char unrelated_file[] = TMP_DIR "/s2d1/unrelated/f1";
+ int ruleset_fd;
+
+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Adds a no-inherit rule on a leaf directory unrelated to s2. */
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d3,
+ LANDLOCK_ADD_RULE_NO_INHERIT);
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ /* Ensure we can still create and delete files outside the sealed branch. */
+ ASSERT_EQ(0, mkdir(unrelated_dir, 0700));
+ ASSERT_EQ(0, mknod(unrelated_file, S_IFREG | 0600, 0));
+ ASSERT_EQ(0, unlink(unrelated_file));
+ ASSERT_EQ(0, rmdir(unrelated_dir));
+
+ /* Existing siblings in s2 remain modifiable. */
+ ASSERT_EQ(0, unlink(file1_s2d1));
+ ASSERT_EQ(0, mknod(file1_s2d1, S_IFREG | 0700, 0));
+}
+
+TEST_F_FORK(layout1, inherit_no_inherit_topology_file)
+{
+ const struct rule rules[] = {
+ {
+ .path = TMP_DIR,
+ .access = ACCESS_RW,
+ },
+ {},
+ };
+ int ruleset_fd;
+ struct landlock_path_beneath_attr path_beneath = {
+ .allowed_access = ACCESS_RO,
+ };
+
+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+ ASSERT_LE(0, ruleset_fd);
+
+ path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC);
+ ASSERT_LE(0, path_beneath.parent_fd);
+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+ &path_beneath,
+ LANDLOCK_ADD_RULE_NO_INHERIT));
+ ASSERT_EQ(EINVAL, errno);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+ ASSERT_EQ(0, close(ruleset_fd));
+}
+
+TEST_F_FORK(layout1, inherit_no_inherit_layered)
+{
+ const struct rule layer1[] = {
+ {
+ .path = TMP_DIR,
+ .access = ACCESS_RW,
+ },
+ {},
+ };
+ int ruleset_fd;
+ static const char unrelated_dir[] = TMP_DIR "/s2d1/unrelated";
+ static const char unrelated_file[] = TMP_DIR "/s2d1/unrelated/f1";
+
+ /* Layer 1: RW on TMP_DIR */
+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1);
+ ASSERT_LE(0, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ /* Layer 2: Add no-inherit RO rule on s1d2 */
+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1);
+ ASSERT_LE(0, ruleset_fd);
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d2,
+ LANDLOCK_ADD_RULE_NO_INHERIT);
+ enforce_ruleset(_metadata, ruleset_fd);
+ ASSERT_EQ(0, close(ruleset_fd));
+
+ /* Operations in unrelated areas should still work */
+ ASSERT_EQ(0, mkdir(unrelated_dir, 0700));
+ ASSERT_EQ(0, mknod(unrelated_file, S_IFREG | 0600, 0));
+ ASSERT_EQ(0, unlink(unrelated_file));
+ ASSERT_EQ(0, rmdir(unrelated_dir));
+
+ /* Creating in s1d1 should be allowed (parent still has RW) */
+ ASSERT_EQ(0, mknod(TMP_DIR "/s1d1/newfile", S_IFREG | 0600, 0));
+ ASSERT_EQ(0, unlink(TMP_DIR "/s1d1/newfile"));
+
+ /* Content of s1d2 should be read-only */
+ ASSERT_EQ(-1, unlink(file1_s1d2));
+ ASSERT_EQ(EACCES, errno);
+
+ /* Topology changes to s1d2 should be denied */
+ ASSERT_EQ(-1, rename(dir_s1d2, TMP_DIR "/s2d1/renamed"));
+ ASSERT_EQ(EACCES, errno);
+
+ /* Renaming s1d1 should also be denied (it's an ancestor) */
+ ASSERT_EQ(-1, rename(dir_s1d1, TMP_DIR "/s2d1/renamed"));
+ ASSERT_EQ(EACCES, errno);
+}
+
/* clang-format off */
FIXTURE(ioctl) {};
@@ -7647,7 +7845,7 @@ static int apply_a_layer(struct __test_metadata *const _metadata,
continue;
add_path_beneath(_metadata, rs_fd, r->access, r->path,
- r->quiet);
+ r->quiet ? LANDLOCK_ADD_RULE_QUIET : 0);
}
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled.
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
` (3 preceding siblings ...)
2025-11-20 22:23 ` [PATCH 4/6] selftests/landlock: Implement selftests for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
2025-11-22 23:35 ` Tingmao Wang
2025-11-20 22:23 ` [PATCH 6/6] landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
5 siblings, 1 reply; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
This was necessary when fixing the no inherit patch in the implementation of kunit tests.
When compiled without the audit flag enabled, kunit tests would fail to compile because of
the missing quiet flag field. This fixes this issue.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/audit.h | 3 ++-
security/landlock/fs.c | 4 +++-
security/landlock/net.h | 2 +-
security/landlock/ruleset.c | 2 ++
4 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 950365cd223d..1a5a9cd28cf4 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -69,7 +69,8 @@ landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
static inline void
landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+ const struct landlock_request *const request,
+ const struct collected_rule_flags rule_flags)
{
}
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index ebeee080ea7a..20a24f3988bc 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -2094,7 +2094,7 @@ static int hook_path_unlink(const struct path *const dir,
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs, NULL);
int err;
-
+
if (subject) {
err = deny_no_inherit_topology_change(subject, dentry);
if (err)
@@ -2155,6 +2155,7 @@ get_required_file_open_access(const struct file *const file)
static void build_check_file_security(void)
{
+#ifdef CONFIG_AUDIT
const struct landlock_file_security file_sec = {
.quiet_optional_accesses = ~0,
.fown_layer = ~0,
@@ -2171,6 +2172,7 @@ static void build_check_file_security(void)
__const_hweight64(_LANDLOCK_ACCESS_FS_OPTIONAL));
/* Makes sure all layers can be identified. */
BUILD_BUG_ON(file_sec.fown_layer < LANDLOCK_MAX_NUM_LAYERS - 1);
+#endif /* CONFIG_AUDIT */
}
static int hook_file_alloc_security(struct file *const file)
diff --git a/security/landlock/net.h b/security/landlock/net.h
index 799cedd5d0b7..72c47f4d6803 100644
--- a/security/landlock/net.h
+++ b/security/landlock/net.h
@@ -25,7 +25,7 @@ static inline void landlock_add_net_hooks(void)
static inline int
landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
- access_mask_t access_rights)
+ access_mask_t access_rights, const int flags)
{
return -EAFNOSUPPORT;
}
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 63aa420ab593..5d190d6779da 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -660,7 +660,9 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
if (err)
return ERR_PTR(err);
+#ifdef CONFIG_AUDIT
new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
+#endif /* CONFIG_AUDIT */
return no_free_ptr(new_dom);
}
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 6/6] landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
` (4 preceding siblings ...)
2025-11-20 22:23 ` [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled Justin Suess
@ 2025-11-20 22:23 ` Justin Suess
5 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-20 22:23 UTC (permalink / raw)
To: linux-security-module
Cc: Tingmao Wang, Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, Justin Suess
Add a unit test for rule_flag collection, ensuring that access masks are properly
propagated with the flags.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/ruleset.c | 85 +++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 5d190d6779da..c407ec264207 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -22,6 +22,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <uapi/linux/landlock.h>
+#include <kunit/test.h>
#include "access.h"
#include "audit.h"
@@ -835,3 +836,87 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
}
return handled_accesses;
}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_unmask_layers_no_inherit(struct kunit *const test)
+{
+ struct landlock_rule *rule;
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS];
+ struct collected_rule_flags rule_flags;
+ const access_mask_t access_request = BIT_ULL(0) | BIT_ULL(1);
+ const layer_mask_t layers_initialized = BIT_ULL(0) | BIT_ULL(1);
+ size_t i;
+
+ rule = kzalloc(struct_size(rule, layers, 2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, rule);
+
+ rule->num_layers = 2;
+
+ /* Layer 1: allows access 0, no_inherit */
+ rule->layers[0].level = 1;
+ rule->layers[0].access = BIT_ULL(0);
+ rule->layers[0].flags.no_inherit = 1;
+
+ /* Layer 2: allows access 1 */
+ rule->layers[1].level = 2;
+ rule->layers[1].access = BIT_ULL(1);
+
+ /* Case 1: No rule_flags provided (should behave normally) */
+ for (i = 0; i < ARRAY_SIZE(layer_masks); i++)
+ layer_masks[i] = layers_initialized;
+
+ landlock_unmask_layers(rule, access_request, &layer_masks,
+ ARRAY_SIZE(layer_masks), NULL);
+
+ /* Access 0 should be unmasked by layer 1 */
+ KUNIT_EXPECT_EQ(test, layer_masks[0], layers_initialized & ~BIT_ULL(0));
+ /* Access 1 should be unmasked by layer 2 */
+ KUNIT_EXPECT_EQ(test, layer_masks[1], layers_initialized & ~BIT_ULL(1));
+
+ /* Case 2: rule_flags provided, no existing no_inherit_masks */
+ for (i = 0; i < ARRAY_SIZE(layer_masks); i++)
+ layer_masks[i] = layers_initialized;
+ memset(&rule_flags, 0, sizeof(rule_flags));
+
+ landlock_unmask_layers(rule, access_request, &layer_masks,
+ ARRAY_SIZE(layer_masks), &rule_flags);
+
+ /* Access 0 should be unmasked by layer 1 */
+ KUNIT_EXPECT_EQ(test, layer_masks[0], layers_initialized & ~BIT_ULL(0));
+ /* Access 1 should be unmasked by layer 2 */
+ KUNIT_EXPECT_EQ(test, layer_masks[1], layers_initialized & ~BIT_ULL(1));
+
+ /* rule_flags should collect no_inherit from layer 1 */
+ KUNIT_EXPECT_EQ(test, rule_flags.no_inherit_masks, (layer_mask_t)BIT_ULL(0));
+
+ /* Case 3: rule_flags provided, layer 1 is masked by no_inherit_masks */
+ for (i = 0; i < ARRAY_SIZE(layer_masks); i++)
+ layer_masks[i] = layers_initialized;
+ memset(&rule_flags, 0, sizeof(rule_flags));
+ rule_flags.no_inherit_masks = BIT_ULL(0); /* Mask layer 1 */
+
+ landlock_unmask_layers(rule, access_request, &layer_masks,
+ ARRAY_SIZE(layer_masks), &rule_flags);
+
+ /* Access 0 should NOT be unmasked by layer 1 because it is skipped */
+ KUNIT_EXPECT_EQ(test, layer_masks[0], layers_initialized);
+ /* Access 1 should be unmasked by layer 2 */
+ KUNIT_EXPECT_EQ(test, layer_masks[1], layers_initialized & ~BIT_ULL(1));
+
+ kfree(rule);
+}
+
+static struct kunit_case ruleset_test_cases[] = {
+ KUNIT_CASE(test_unmask_layers_no_inherit),
+ {}
+};
+
+static struct kunit_suite ruleset_test_suite = {
+ .name = "landlock_ruleset",
+ .test_cases = ruleset_test_cases,
+};
+
+kunit_test_suite(ruleset_test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
--
2.51.2
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled.
2025-11-20 22:23 ` [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled Justin Suess
@ 2025-11-22 23:35 ` Tingmao Wang
2025-11-23 16:43 ` Justin Suess
0 siblings, 1 reply; 11+ messages in thread
From: Tingmao Wang @ 2025-11-22 23:35 UTC (permalink / raw)
To: Justin Suess
Cc: Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, linux-security-module
On 11/20/25 22:23, Justin Suess wrote:
> This was necessary when fixing the no inherit patch in the implementation of kunit tests.
>
> When compiled without the audit flag enabled, kunit tests would fail to compile because of
> the missing quiet flag field. This fixes this issue.
>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
Thanks for spotting these issues! I will include your fix in the next
version of my patch (it's not merged yet, so mistakes can be fixed "at
source"). Feel free to reply to my patch directly if you spot more
problems :)
(For the landlock_log_denial one, we might not end up changing the
function's parameter after all - Mickaël has suggested the rule_flags be
embedded inside the landlock_request)
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled.
2025-11-22 23:35 ` Tingmao Wang
@ 2025-11-23 16:43 ` Justin Suess
0 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-23 16:43 UTC (permalink / raw)
To: m; +Cc: gnoack, jack, linux-security-module, mic, utilityemal77, xandfury
No problem it was an easy fix. I'll reply directly to your patch in the
future, I'm still figuring out mailing list development and kernel
development workflows.
I do realize now I have some behaviors in this series that impact the
quiet flag that I want your input on. I'll add those as replies to
your patch series as well.
I plan to keep rebasing this patch off the quiet flag series as you release
new versions. I also plan to introduce some selftests combining the two flags
and ensuring they interact with eachother as expected.
The next version of this patch is going to include some big refactorings
(most likely removing the xarrays) and fix some edge cases I discovered.
King Regards,
Justin Suess
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api
2025-11-20 22:23 ` [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api Justin Suess
@ 2025-11-23 21:03 ` Tingmao Wang
2025-11-25 12:06 ` Justin Suess
0 siblings, 1 reply; 11+ messages in thread
From: Tingmao Wang @ 2025-11-23 21:03 UTC (permalink / raw)
To: Justin Suess
Cc: Günther Noack, Jan Kara, Abhinav Saxena,
Mickaël Salaün, linux-security-module
On 11/20/25 22:23, Justin Suess wrote:
> Implements the syscall side flag handling and kernel api headers for the
> LANDLOCK_ADD_RULE_NO_INHERIT flag.
I guess you probably want to change the comment in add_rule_* as well:
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 200287a34895..650ffce6f92e 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -352,7 +352,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
+ * is there to hold a quiet or no inherit flag.
*/
if (!flags && !path_beneath_attr.allowed_access)
return -ENOMSG;
@@ -393,7 +393,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
* are ignored by network actions. However, the rule is not useless
- * if it is there to hold a quiet flag
+ * if it is there to hold a quiet or no inherit flag.
*/
if (!flags && !net_port_attr.allowed_access)
return -ENOMSG;
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api
2025-11-23 21:03 ` Tingmao Wang
@ 2025-11-25 12:06 ` Justin Suess
0 siblings, 0 replies; 11+ messages in thread
From: Justin Suess @ 2025-11-25 12:06 UTC (permalink / raw)
To: m; +Cc: gnoack, jack, linux-security-module, mic, utilityemal77, xandfury
Good catch.
Probably just gonna add that comment to the add_rule_path_beneath
since LANDLOCK_ADD_RULE_NO_INHERIT doesn't really apply to networking
stuff at all and really doesn't make sense in those rules.
I may even include some code barring the flag from being included in
irrelevant scopes.
Networking, sockets, and signals don't really have an inheritance
behavior.
I personally don't really see how this flag could apply to any
other scopes but if anyone has ideas I'd love to hear them.
If other hierarchical scopes get added then this flag can support those.
Or maybe this flag can have in a different meaning in those contexts.
Thank You,
Justin Suess
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-11-25 12:07 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-20 22:23 [PATCH v2 0/6] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2025-11-20 22:23 ` [PATCH 1/6] landlock: " Justin Suess
2025-11-20 22:23 ` [PATCH 2/6] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api Justin Suess
2025-11-23 21:03 ` Tingmao Wang
2025-11-25 12:06 ` Justin Suess
2025-11-20 22:23 ` [PATCH 3/6] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer Justin Suess
2025-11-20 22:23 ` [PATCH 4/6] selftests/landlock: Implement selftests for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2025-11-20 22:23 ` [PATCH 5/6] landlock: Fix compilation error for kunit tests when CONFIG_AUDIT is disabled Justin Suess
2025-11-22 23:35 ` Tingmao Wang
2025-11-23 16:43 ` Justin Suess
2025-11-20 22:23 ` [PATCH 6/6] landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).