summaryrefslogtreecommitdiff
path: root/security/landlock/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/landlock/fs.c')
-rw-r--r--security/landlock/fs.c46
1 files changed, 32 insertions, 14 deletions
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cee2b6f22c83..fe794875ad46 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -714,7 +714,8 @@ static void test_is_eacces_with_write(struct kunit *const test)
* is_access_to_paths_allowed - Check accesses for requests with a common path
*
* @domain: Domain to check against.
- * @path: File hierarchy to walk through.
+ * @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
@@ -837,7 +838,6 @@ static bool is_access_to_paths_allowed(
* restriction.
*/
while (true) {
- struct dentry *parent_dentry;
const struct landlock_rule *rule;
/*
@@ -909,21 +909,33 @@ jump_up:
break;
}
}
+
if (unlikely(IS_ROOT(walker_path.dentry))) {
- /*
- * Stops at disconnected root directories. Only allows
- * access to internal filesystems (e.g. nsfs, which is
- * reachable through /proc/<pid>/ns/<namespace>).
- */
- if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
+ 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;
}
- 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;
}
- parent_dentry = dget_parent(walker_path.dentry);
- dput(walker_path.dentry);
- walker_path.dentry = parent_dentry;
}
path_put(&walker_path);
@@ -1021,6 +1033,9 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
* 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.
@@ -1062,8 +1077,11 @@ static bool collect_domain_accesses(
break;
}
- /* We should not reach a root other than @mnt_root. */
- if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir)))
+ /*
+ * 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);