* [PATCH v2 0/2] landlock: Simplify path walk logic
@ 2026-03-03 4:05 Justin Suess
2026-03-03 4:05 ` [PATCH v2 1/2] landlock: Add path walk helper Justin Suess
2026-03-03 4:05 ` [PATCH v2 2/2] landlock: Refactor path access checks Justin Suess
0 siblings, 2 replies; 3+ messages in thread
From: Justin Suess @ 2026-03-03 4:05 UTC (permalink / raw)
To: linux-security-module, Mickaël Salaün
Cc: Günther Noack, Tingmao Wang, Justin Suess
Hello,
This is v2 of a 2-patch Landlock cleanup series for security/landlock/fs.c.
These patches simplify path handling and path-based access checks. The cleanup
was initially part of my LANDLOCK_ADD_RULE_NO_INHERIT work, but it is more
appropriate as a separate preparatory series.
v1: https://lore.kernel.org/linux-security-module/20260218201857.1194667-1-utilityemal77@gmail.com/
Changes since v1
-----
- Split and clarified the series as:
1) "landlock: Add path walk helper"
2) "landlock: Refactor path access checks"
- Introduced landlock_walk_path_up() and enum landlock_walk_result to
centralize upward path traversal state.
- Refactored is_access_to_paths_allowed() and callers around a
struct landlock_check state object.
- Inlined collect_domain_accesses() into current_check_refer_path() and
reused current_check_access_path() for same-directory refer checks.
Motivation
-----
Current path-walk logic is tightly coupled to specific call sites and is hard
to read and maintain. Centralizing traversal and per-check state should make
future Landlock path-based features easier to implement and review, including
follow-up work related to no-inherit semantics.
Patch overview
-----
Patch 1 introduces landlock_walk_path_up(), which walks a struct path
toward the VFS root and reports whether the current position is an internal
mount point, the real root, or neither.
Patch 2 applies the helper to access-check paths, introduces a struct landlock_check
to aggregate reused local variables used to check accesses, and removes collect_domain_accesses()
as a standalone helper.
Patch 1 can be applied independently. Patch 2 contains some more aggressive trimming
and refactoring of logic enabled by the first patch.
Thank you for your time.
Kind regards,
Justin Suess
Justin Suess (2):
landlock: Add path walk helper
landlock: Refactor path access checks
security/landlock/fs.c | 507 ++++++++++++++++++-----------------------
1 file changed, 225 insertions(+), 282 deletions(-)
base-commit: 8ff74a72b8af3672beca7f6b6b72557a9db94382
--
2.51.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] landlock: Add path walk helper
2026-03-03 4:05 [PATCH v2 0/2] landlock: Simplify path walk logic Justin Suess
@ 2026-03-03 4:05 ` Justin Suess
2026-03-03 4:05 ` [PATCH v2 2/2] landlock: Refactor path access checks Justin Suess
1 sibling, 0 replies; 3+ messages in thread
From: Justin Suess @ 2026-03-03 4:05 UTC (permalink / raw)
To: linux-security-module, Mickaël Salaün
Cc: Günther Noack, Tingmao Wang, Justin Suess
Add path walk helper landlock_walk_path_up. This helper takes a pointer
to a struct path and walks the path upward towards the VFS root, and
returns an enum corresponding whether the current position in the walk
is an internal mountpoint, the real root, or neither.
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/fs.c | 92 ++++++++++++++++++++++++------------------
1 file changed, 52 insertions(+), 40 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index e764470f588c..180ab149be74 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -317,6 +317,38 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
LANDLOCK_ACCESS_FS_IOCTL_DEV)
/* clang-format on */
+/**
+ * enum landlock_walk_result - Result codes for landlock_walk_path_up()
+ * @LANDLOCK_WALK_CONTINUE: Path is now neither the real root nor an internal mount point.
+ * @LANDLOCK_WALK_STOP_REAL_ROOT: Path has reached the real VFS root.
+ * @LANDLOCK_WALK_INTERNAL: Path has reached an internal mount point.
+ */
+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))) {
+ if (likely(path->mnt->mnt_flags & MNT_INTERNAL))
+ return LANDLOCK_WALK_INTERNAL;
+ path->dentry = dget(path->mnt->mnt_root);
+ } else {
+ path->dentry = dget_parent(old);
+ }
+ dput(old);
+ return LANDLOCK_WALK_CONTINUE;
+}
+
/*
* @path: Should have been checked by get_path_from_fd().
*/
@@ -874,47 +906,27 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
/* Stops when a rule from each layer grants access. */
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;
- }
-
- /*
- * We reached a disconnected root directory from a bind mount.
- * Let's continue the walk with the mount point we missed.
- */
- 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;
+ switch (landlock_walk_path_up(&walker_path)) {
+ /*
+ * 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>).
+ */
+ case LANDLOCK_WALK_INTERNAL:
+ allowed_parent1 = true;
+ allowed_parent2 = true;
+ break;
+ /*
+ * Stops at the real root. Denies access
+ * because not all layers have granted access
+ */
+ case LANDLOCK_WALK_STOP_REAL_ROOT:
+ break;
+ /* Otherwise, keep walking up to the root. */
+ case LANDLOCK_WALK_CONTINUE:
+ continue;
}
+ break;
}
path_put(&walker_path);
--
2.51.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] landlock: Refactor path access checks
2026-03-03 4:05 [PATCH v2 0/2] landlock: Simplify path walk logic Justin Suess
2026-03-03 4:05 ` [PATCH v2 1/2] landlock: Add path walk helper Justin Suess
@ 2026-03-03 4:05 ` Justin Suess
1 sibling, 0 replies; 3+ messages in thread
From: Justin Suess @ 2026-03-03 4:05 UTC (permalink / raw)
To: linux-security-module, Mickaël Salaün
Cc: Günther Noack, Tingmao Wang, Justin Suess
Introduce struct landlock_check to bundle per-check state,
refactor is_access_to_paths_allowed() and its callers,
inline collect_domain_accesses() into current_check_refer_path(),
and reuse current_check_access_path() for same-directory refer checks.
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/fs.c | 419 +++++++++++++++++------------------------
1 file changed, 175 insertions(+), 244 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 180ab149be74..7526414501ed 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -430,6 +430,14 @@ static const struct access_masks any_fs = {
.fs = ~0,
};
+struct landlock_check {
+ access_mask_t access_request;
+ access_mask_t access_masked;
+ struct layer_access_masks layer_masks;
+ struct landlock_request request;
+ bool allowed;
+};
+
/*
* Returns true iff the child file with the given src_child access rights under
* src_parent would result in having the same or fewer access rights if it were
@@ -734,28 +742,17 @@ static void test_is_eacces_with_write(struct kunit *const test)
* @domain: Domain to check against.
* @path: File hierarchy to walk through. For refer checks, this would be
* the common mountpoint.
- * @access_request_parent1: Accesses to check, once @layer_masks_parent1 is
- * equal to @layer_masks_parent2 (if any). This is tied to the unique
- * requested path for most actions, or the source in case of a refer action
- * (i.e. rename or link), or the source and destination in case of
- * RENAME_EXCHANGE.
- * @layer_masks_parent1: Pointer to a matrix of layer masks per access
- * masks, identifying the layers that forbid a specific access. Bits from
- * this matrix can be unset according to the @path walk. An empty matrix
- * means that @domain allows all possible Landlock accesses (i.e. not only
- * those identified by @access_request_parent1). This matrix can
- * initially refer to domain layer masks and, when the accesses for the
- * destination and source are the same, to requested layer masks.
- * @log_request_parent1: Audit request to fill if the related access is denied.
+ * @check_parent1: Accesses and associated metadata to check, once
+ * @check_parent1.layer_masks is equal to @check_parent2.layer_masks
+ * (if any). This is tied to the unique requested path for most actions,
+ * or the source in case of a refer action (i.e. rename or link), or the
+ * source and destination in case of RENAME_EXCHANGE.
* @dentry_child1: Dentry to the initial child of the parent1 path. This
* pointer must be NULL for non-refer actions (i.e. not link nor rename).
- * @access_request_parent2: Similar to @access_request_parent1 but for a
- * request involving a source and a destination. This refers to the
- * destination, except in case of RENAME_EXCHANGE where it also refers to
- * the source. Must be set to 0 when using a simple path request.
- * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer
- * action. This must be NULL otherwise.
- * @log_request_parent2: Audit request to fill if the related access is denied.
+ * @check_parent2: Similar to @check_parent1 but for a request involving a
+ * source and a destination. This refers to the destination, except in
+ * case of RENAME_EXCHANGE where it also refers to the source. This must
+ * be NULL when using a simple path request.
* @dentry_child2: Dentry to the initial child of the parent2 path. This
* pointer is only set for RENAME_EXCHANGE actions and must be NULL
* otherwise.
@@ -773,24 +770,24 @@ static void test_is_eacces_with_write(struct kunit *const test)
static bool
is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
const struct path *const path,
- const access_mask_t access_request_parent1,
- struct layer_access_masks *layer_masks_parent1,
- struct landlock_request *const log_request_parent1,
+ struct landlock_check *const check_parent1,
struct dentry *const dentry_child1,
- const access_mask_t access_request_parent2,
- struct layer_access_masks *layer_masks_parent2,
- struct landlock_request *const log_request_parent2,
+ struct landlock_check *const check_parent2,
struct dentry *const dentry_child2)
{
- bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
- child1_is_directory = true, child2_is_directory = true;
+ struct landlock_check *const checks[] = {
+ check_parent1,
+ check_parent2,
+ };
+ struct layer_access_masks layer_masks_child[2] = {};
+ bool is_dom_check, child_is_directory[2] = { true, true };
struct path walker_path;
- access_mask_t access_masked_parent1, access_masked_parent2;
- struct layer_access_masks _layer_masks_child1, _layer_masks_child2;
- struct layer_access_masks *layer_masks_child1 = NULL,
- *layer_masks_child2 = NULL;
- if (!access_request_parent1 && !access_request_parent2)
+ if (WARN_ON_ONCE(!check_parent1))
+ return false;
+
+ if (!check_parent1->access_request &&
+ (!check_parent2 || !check_parent2->access_request))
return true;
if (WARN_ON_ONCE(!path))
@@ -799,51 +796,45 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
if (is_nouser_or_private(path->dentry))
return true;
- if (WARN_ON_ONCE(!layer_masks_parent1))
- return false;
-
- allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+ check_parent1->allowed =
+ is_layer_masks_allowed(&check_parent1->layer_masks);
+ if (check_parent2)
+ check_parent2->allowed =
+ is_layer_masks_allowed(&check_parent2->layer_masks);
- if (unlikely(layer_masks_parent2)) {
+ if (unlikely(check_parent2)) {
if (WARN_ON_ONCE(!dentry_child1))
return false;
- allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
-
/*
* For a double request, first check for potential privilege
* escalation by looking at domain handled accesses (which are
* a superset of the meaningful requested accesses).
*/
- access_masked_parent1 = access_masked_parent2 =
- landlock_union_access_masks(domain).fs;
+ for (size_t i = 0; i < ARRAY_SIZE(checks); i++)
+ checks[i]->access_masked =
+ landlock_union_access_masks(domain).fs;
is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
return false;
/* For a simple request, only check for requested accesses. */
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
+ check_parent1->access_masked = check_parent1->access_request;
is_dom_check = false;
}
- if (unlikely(dentry_child1)) {
- if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- &_layer_masks_child1,
- LANDLOCK_KEY_INODE))
- landlock_unmask_layers(find_rule(domain, dentry_child1),
- &_layer_masks_child1);
- layer_masks_child1 = &_layer_masks_child1;
- child1_is_directory = d_is_dir(dentry_child1);
- }
- if (unlikely(dentry_child2)) {
+ for (size_t i = 0; i < ARRAY_SIZE(layer_masks_child); i++) {
+ const struct dentry *const dentry_child =
+ i ? dentry_child2 : dentry_child1;
+
+ if (unlikely(!dentry_child))
+ continue;
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- &_layer_masks_child2,
+ &layer_masks_child[i],
LANDLOCK_KEY_INODE))
- landlock_unmask_layers(find_rule(domain, dentry_child2),
- &_layer_masks_child2);
- layer_masks_child2 = &_layer_masks_child2;
- child2_is_directory = d_is_dir(dentry_child2);
+ landlock_unmask_layers(find_rule(domain, dentry_child),
+ &layer_masks_child[i]);
+ child_is_directory[i] = d_is_dir(dentry_child);
}
walker_path = *path;
@@ -869,42 +860,47 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
*/
if (unlikely(is_dom_check &&
no_more_access(
- layer_masks_parent1, layer_masks_child1,
- child1_is_directory, layer_masks_parent2,
- layer_masks_child2,
- child2_is_directory))) {
+ &check_parent1->layer_masks,
+ dentry_child1 ? &layer_masks_child[0] : NULL,
+ child_is_directory[0],
+ check_parent2 ?
+ &check_parent2->layer_masks :
+ NULL,
+ dentry_child2 ? &layer_masks_child[1] : NULL,
+ child_is_directory[1]))) {
/*
* Now, downgrades the remaining checks from domain
* handled accesses to requested accesses.
*/
is_dom_check = false;
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
-
- allowed_parent1 =
- allowed_parent1 ||
- scope_to_request(access_masked_parent1,
- layer_masks_parent1);
- allowed_parent2 =
- allowed_parent2 ||
- scope_to_request(access_masked_parent2,
- layer_masks_parent2);
+ for (size_t i = 0; i < ARRAY_SIZE(checks); i++) {
+ if (!checks[i])
+ continue;
+ checks[i]->access_masked = checks[i]->access_request;
+ checks[i]->allowed =
+ checks[i]->allowed ||
+ scope_to_request(checks[i]->access_masked,
+ &checks[i]->layer_masks);
+ }
/* Stops when all accesses are granted. */
- if (allowed_parent1 && allowed_parent2)
+ if (check_parent1->allowed &&
+ (!check_parent2 || check_parent2->allowed))
break;
}
rule = find_rule(domain, walker_path.dentry);
- allowed_parent1 =
- allowed_parent1 ||
- landlock_unmask_layers(rule, layer_masks_parent1);
- allowed_parent2 =
- allowed_parent2 ||
- landlock_unmask_layers(rule, layer_masks_parent2);
+ for (size_t i = 0; i < ARRAY_SIZE(checks); i++) {
+ if (!checks[i])
+ continue;
+ checks[i]->allowed =
+ checks[i]->allowed ||
+ landlock_unmask_layers(rule, &checks[i]->layer_masks);
+ }
/* Stops when a rule from each layer grants access. */
- if (allowed_parent1 && allowed_parent2)
+ if (check_parent1->allowed &&
+ (!check_parent2 || check_parent2->allowed))
break;
switch (landlock_walk_path_up(&walker_path)) {
/*
@@ -913,8 +909,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
* which is reachable through /proc/<pid>/ns/<namespace>).
*/
case LANDLOCK_WALK_INTERNAL:
- allowed_parent1 = true;
- allowed_parent2 = true;
+ check_parent1->allowed = true;
+ if (check_parent2)
+ check_parent2->allowed = true;
break;
/*
* Stops at the real root. Denies access
@@ -931,28 +928,25 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
path_put(&walker_path);
/*
- * Check CONFIG_AUDIT to enable elision of log_request_parent* and
- * associated caller's stack variables thanks to dead code elimination.
+ * Check CONFIG_AUDIT to enable elision of request fields and related
+ * caller stack usage thanks to dead code elimination.
*/
#ifdef CONFIG_AUDIT
- if (!allowed_parent1 && log_request_parent1) {
- log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
- log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
- log_request_parent1->audit.u.path = *path;
- log_request_parent1->access = access_masked_parent1;
- log_request_parent1->layer_masks = layer_masks_parent1;
- }
+ for (size_t i = 0; i < 2; i++) {
+ struct landlock_check *const check = checks[i];
+
+ if (!check || check->allowed)
+ continue;
- if (!allowed_parent2 && log_request_parent2) {
- log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS;
- log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH;
- log_request_parent2->audit.u.path = *path;
- log_request_parent2->access = access_masked_parent2;
- log_request_parent2->layer_masks = layer_masks_parent2;
+ check->request.type = LANDLOCK_REQUEST_FS_ACCESS;
+ check->request.audit.type = LSM_AUDIT_DATA_PATH;
+ check->request.audit.u.path = *path;
+ check->request.access = check->access_masked;
+ check->request.layer_masks = &check->layer_masks;
}
#endif /* CONFIG_AUDIT */
- return allowed_parent1 && allowed_parent2;
+ return check_parent1->allowed && (!check_parent2 || check_parent2->allowed);
}
static int current_check_access_path(const struct path *const path,
@@ -963,21 +957,22 @@ static int current_check_access_path(const struct path *const path,
};
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), masks, NULL);
- struct layer_access_masks layer_masks;
- struct landlock_request request = {};
+ struct landlock_check check = {
+ .access_request = access_request,
+ };
if (!subject)
return 0;
- access_request = landlock_init_layer_masks(subject->domain,
- access_request, &layer_masks,
- LANDLOCK_KEY_INODE);
- if (is_access_to_paths_allowed(subject->domain, path, access_request,
- &layer_masks, &request, NULL, 0, NULL,
+ check.access_request =
+ landlock_init_layer_masks(subject->domain, check.access_request,
+ &check.layer_masks,
+ LANDLOCK_KEY_INODE);
+ if (is_access_to_paths_allowed(subject->domain, path, &check, NULL,
NULL, NULL))
return 0;
- landlock_log_denial(subject, &request);
+ landlock_log_denial(subject, &check.request);
return -EACCES;
}
@@ -1013,77 +1008,6 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
LANDLOCK_ACCESS_FS_REMOVE_FILE;
}
-/**
- * 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.
- * @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.
- *
- * Because of disconnected directories, this walk may not reach @mnt_dir. In
- * this case, the walk will continue to @mnt_dir 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.
- *
- * Returns:
- * - true if all the domain access rights are allowed for @dir;
- * - false if the walk reached @mnt_root.
- */
-static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
- const struct dentry *const mnt_root,
- struct dentry *dir,
- struct layer_access_masks *layer_masks_dom)
-{
- bool ret = false;
-
- if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
- return true;
- if (is_nouser_or_private(dir))
- return true;
-
- if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- layer_masks_dom, LANDLOCK_KEY_INODE))
- return true;
-
- dget(dir);
- while (true) {
- struct dentry *parent_dentry;
-
- /* Gets all layers allowing all domain accesses. */
- if (landlock_unmask_layers(find_rule(domain, dir),
- layer_masks_dom)) {
- /*
- * Stops when all handled accesses are allowed by at
- * least one rule in each layer.
- */
- ret = true;
- break;
- }
-
- /*
- * Stops at the mount point or the filesystem root for a disconnected
- * directory.
- */
- if (dir == mnt_root || unlikely(IS_ROOT(dir)))
- break;
-
- parent_dentry = dget_parent(dir);
- dput(dir);
- dir = parent_dentry;
- }
- dput(dir);
- return ret;
-}
-
/**
* current_check_refer_path - Check if a rename or link action is allowed
*
@@ -1144,32 +1068,24 @@ static int current_check_refer_path(struct dentry *const old_dentry,
{
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs, NULL);
- bool allow_parent1, allow_parent2;
- access_mask_t access_request_parent1, access_request_parent2;
struct path mnt_dir;
- struct dentry *old_parent;
- struct layer_access_masks layer_masks_parent1 = {},
- layer_masks_parent2 = {};
- struct landlock_request request1 = {}, request2 = {};
+ struct path old_parent_path;
+ struct landlock_check checks[2] = {};
if (!subject)
return 0;
if (unlikely(d_is_negative(old_dentry)))
return -ENOENT;
- if (exchange) {
- if (unlikely(d_is_negative(new_dentry)))
- return -ENOENT;
- access_request_parent1 =
- get_mode_access(d_backing_inode(new_dentry)->i_mode);
- } else {
- access_request_parent1 = 0;
- }
- access_request_parent2 =
+ if (exchange && unlikely(d_is_negative(new_dentry)))
+ return -ENOENT;
+ checks[0].access_request =
+ exchange ? get_mode_access(d_backing_inode(new_dentry)->i_mode) : 0;
+ checks[1].access_request =
get_mode_access(d_backing_inode(old_dentry)->i_mode);
if (removable) {
- access_request_parent1 |= maybe_remove(old_dentry);
- access_request_parent2 |= maybe_remove(new_dentry);
+ checks[0].access_request |= maybe_remove(old_dentry);
+ checks[1].access_request |= maybe_remove(new_dentry);
}
/* The mount points are the same for old and new paths, cf. EXDEV. */
@@ -1178,22 +1094,12 @@ static int current_check_refer_path(struct dentry *const old_dentry,
* The LANDLOCK_ACCESS_FS_REFER access right is not required
* for same-directory referer (i.e. no reparenting).
*/
- access_request_parent1 = landlock_init_layer_masks(
- subject->domain,
- access_request_parent1 | access_request_parent2,
- &layer_masks_parent1, LANDLOCK_KEY_INODE);
- if (is_access_to_paths_allowed(subject->domain, new_dir,
- access_request_parent1,
- &layer_masks_parent1, &request1,
- NULL, 0, NULL, NULL, NULL))
- return 0;
-
- landlock_log_denial(subject, &request1);
- return -EACCES;
+ return current_check_access_path(new_dir,
+ checks[0].access_request | checks[1].access_request);
}
- access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
- access_request_parent2 |= LANDLOCK_ACCESS_FS_REFER;
+ checks[0].access_request |= LANDLOCK_ACCESS_FS_REFER;
+ checks[1].access_request |= LANDLOCK_ACCESS_FS_REFER;
/* Saves the common mount point. */
mnt_dir.mnt = new_dir->mnt;
@@ -1202,21 +1108,46 @@ 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
+ * OPEN_TREE_CLONE). We do not need to path_get(old_parent_path) because
* we keep a reference to old_dentry.
*/
- old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry :
- old_dentry->d_parent;
+ old_parent_path.mnt = mnt_dir.mnt;
+ old_parent_path.dentry = unlikely(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,
- &layer_masks_parent1);
- allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
- new_dir->dentry,
- &layer_masks_parent2);
-
- if (allow_parent1 && allow_parent2)
+ for (size_t i = 0; i < 2; i++) {
+ struct path parent_path = i ? *new_dir : old_parent_path;
+ struct landlock_check *const check = &checks[i];
+
+ if (is_nouser_or_private(parent_path.dentry) ||
+ !landlock_init_layer_masks(subject->domain,
+ LANDLOCK_MASK_ACCESS_FS,
+ &check->layer_masks,
+ LANDLOCK_KEY_INODE)) {
+ check->allowed = true;
+ continue;
+ }
+ path_get(&parent_path);
+ do {
+ /* Gets all layers allowing all domain accesses. */
+ if (landlock_unmask_layers(find_rule(subject->domain,
+ parent_path.dentry),
+ &check->layer_masks)) {
+ /*
+ * Stops when all handled accesses are
+ * allowed by at least one rule in each
+ * layer.
+ */
+ check->allowed = true;
+ break;
+ }
+ } while (landlock_walk_path_up(&parent_path) ==
+ LANDLOCK_WALK_CONTINUE);
+ path_put(&parent_path);
+ }
+ if (checks[0].allowed && checks[1].allowed)
return 0;
/*
@@ -1226,27 +1157,26 @@ static int current_check_refer_path(struct dentry *const old_dentry,
* destination parent access rights.
*/
if (is_access_to_paths_allowed(
- subject->domain, &mnt_dir, access_request_parent1,
- &layer_masks_parent1, &request1, old_dentry,
- access_request_parent2, &layer_masks_parent2, &request2,
+ subject->domain, &mnt_dir, &checks[0], old_dentry,
+ &checks[1],
exchange ? new_dentry : NULL))
return 0;
- if (request1.access) {
- request1.audit.u.path.dentry = old_parent;
- landlock_log_denial(subject, &request1);
+ if (checks[0].request.access) {
+ checks[0].request.audit.u.path.dentry = old_parent_path.dentry;
+ landlock_log_denial(subject, &checks[0].request);
}
- if (request2.access) {
- request2.audit.u.path.dentry = new_dir->dentry;
- landlock_log_denial(subject, &request2);
+ if (checks[1].request.access) {
+ checks[1].request.audit.u.path.dentry = new_dir->dentry;
+ landlock_log_denial(subject, &checks[1].request);
}
/*
* This prioritizes EACCES over EXDEV for all actions, including
* renames with RENAME_EXCHANGE.
*/
- if (likely(is_eacces(&layer_masks_parent1, access_request_parent1) ||
- is_eacces(&layer_masks_parent2, access_request_parent2)))
+ if (likely(is_eacces(&checks[0].layer_masks, checks[0].access_request) ||
+ is_eacces(&checks[1].layer_masks, checks[1].access_request)))
return -EACCES;
/*
@@ -1625,12 +1555,11 @@ static bool is_device(const struct file *const file)
static int hook_file_open(struct file *const file)
{
- struct layer_access_masks layer_masks = {};
+ struct landlock_check check = {};
access_mask_t open_access_request, full_access_request, allowed_access,
optional_access;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(file->f_cred, any_fs, NULL);
- struct landlock_request request = {};
if (!subject)
return 0;
@@ -1651,13 +1580,14 @@ static int hook_file_open(struct file *const file)
optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
full_access_request = open_access_request | optional_access;
+ check.access_request =
+ landlock_init_layer_masks(subject->domain, full_access_request,
+ &check.layer_masks,
+ LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(
- subject->domain, &file->f_path,
- landlock_init_layer_masks(subject->domain,
- full_access_request, &layer_masks,
- LANDLOCK_KEY_INODE),
- &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
+ subject->domain, &file->f_path, &check, NULL, NULL,
+ NULL)) {
allowed_access = full_access_request;
} else {
/*
@@ -1666,8 +1596,8 @@ static int hook_file_open(struct file *const file)
* are still unfulfilled in any of the layers.
*/
allowed_access = full_access_request;
- for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++)
- allowed_access &= ~layer_masks.access[i];
+ for (size_t i = 0; i < ARRAY_SIZE(check.layer_masks.access); i++)
+ allowed_access &= ~check.layer_masks.access[i];
}
/*
@@ -1679,15 +1609,16 @@ static int hook_file_open(struct file *const file)
landlock_file(file)->allowed_access = allowed_access;
#ifdef CONFIG_AUDIT
landlock_file(file)->deny_masks = landlock_get_deny_masks(
- _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
+ _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access,
+ &check.layer_masks);
#endif /* CONFIG_AUDIT */
if (access_mask_subset(open_access_request, allowed_access))
return 0;
/* Sets access to reflect the actual request. */
- request.access = open_access_request;
- landlock_log_denial(subject, &request);
+ check.request.access = open_access_request;
+ landlock_log_denial(subject, &check.request);
return -EACCES;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-03-03 4:06 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-03 4:05 [PATCH v2 0/2] landlock: Simplify path walk logic Justin Suess
2026-03-03 4:05 ` [PATCH v2 1/2] landlock: Add path walk helper Justin Suess
2026-03-03 4:05 ` [PATCH v2 2/2] landlock: Refactor path access checks Justin Suess
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox