Skip to content

Commit 49c9e09

Browse files
committed
landlock: Fix handling of disconnected directories
Disconnected files or directories can appear when they are visible and opened from a bind mount, but have been renamed or moved from the source of the bind mount in a way that makes them inaccessible from the mount point (i.e. out of scope). Previously, access rights tied to files or directories opened through a disconnected directory were collected by walking the related hierarchy down to the root of the filesystem, without taking into account the mount point because it couldn't be found. This could lead to inconsistent access results, potential access right widening, and hard-to-debug renames, especially since such paths cannot be printed. For a sandboxed task to create a disconnected directory, it needs to have write access (i.e. FS_MAKE_REG, FS_REMOVE_FILE, and FS_REFER) to the underlying source of the bind mount, and read access to the related mount point. Because a sandboxed task cannot acquire more access rights than those defined by its Landlock domain, this could lead to inconsistent access rights due to missing permissions that should be inherited from the mount point hierarchy, while inheriting permissions from the filesystem hierarchy hidden by this mount point instead. Landlock now handles files and directories opened from disconnected directories by taking into account the filesystem hierarchy when the mount point is not found in the hierarchy walk, and also always taking into account the mount point from which these disconnected directories were opened. This ensures that a rename is not allowed if it would widen access rights [1]. The rationale is that, even if disconnected hierarchies might not be visible or accessible to a sandboxed task, relying on the collected access rights from them improves the guarantee that access rights will not be widened during a rename because of the access right comparison between the source and the destination (see LANDLOCK_ACCESS_FS_REFER). It may look like this would grant more access on disconnected files and directories, but the security policies are always enforced for all the evaluated hierarchies. This new behavior should be less surprising to users and safer from an access control perspective. Remove a wrong WARN_ON_ONCE() canary in collect_domain_accesses() and fix the related comment. Because opened files have their access rights stored in the related file security properties, there is no impact for disconnected or unlinked files. Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Song Liu <song@kernel.org> Reported-by: Tingmao Wang <m@maowtm.org> Closes: https://lore.kernel.org/r/027d5190-b37a-40a8-84e9-4ccbc352bcdf@maowtm.org Closes: https://lore.kernel.org/r/09b24128f86973a6022e6aa8338945fcfb9a33e4.1749925391.git.m@maowtm.org Fixes: b91c3e4 ("landlock: Add support for file reparenting with LANDLOCK_ACCESS_FS_REFER") Fixes: cb2c7d1 ("landlock: Support filesystem access-control") Link: https://lore.kernel.org/r/b0f46246-f2c5-42ca-93ce-0d629702a987@maowtm.org [1] Reviewed-by: Tingmao Wang <m@maowtm.org> Link: https://lore.kernel.org/r/20251128172200.760753-2-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent e614622 commit 49c9e09

2 files changed

Lines changed: 44 additions & 12 deletions

File tree

security/landlock/errata/abi-1.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* SPDX-License-Identifier: GPL-2.0-only */
2+
3+
/**
4+
* DOC: erratum_3
5+
*
6+
* Erratum 3: Disconnected directory handling
7+
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8+
*
9+
* This fix addresses an issue with disconnected directories that occur when a
10+
* directory is moved outside the scope of a bind mount. The change ensures
11+
* that evaluated access rights include both those from the disconnected file
12+
* hierarchy down to its filesystem root and those from the related mount point
13+
* hierarchy. This prevents access right widening through rename or link
14+
* actions.
15+
*/
16+
LANDLOCK_ERRATUM(3)

security/landlock/fs.c

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -910,21 +910,31 @@ static bool is_access_to_paths_allowed(
910910
break;
911911
}
912912
}
913+
913914
if (unlikely(IS_ROOT(walker_path.dentry))) {
914-
/*
915-
* Stops at disconnected root directories. Only allows
916-
* access to internal filesystems (e.g. nsfs, which is
917-
* reachable through /proc/<pid>/ns/<namespace>).
918-
*/
919-
if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
915+
if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
916+
/*
917+
* Stops and allows access when reaching disconnected root
918+
* directories that are part of internal filesystems (e.g. nsfs,
919+
* which is reachable through /proc/<pid>/ns/<namespace>).
920+
*/
920921
allowed_parent1 = true;
921922
allowed_parent2 = true;
923+
break;
922924
}
923-
break;
925+
926+
/*
927+
* We reached a disconnected root directory from a bind mount.
928+
* Let's continue the walk with the mount point we missed.
929+
*/
930+
dput(walker_path.dentry);
931+
walker_path.dentry = walker_path.mnt->mnt_root;
932+
dget(walker_path.dentry);
933+
} else {
934+
parent_dentry = dget_parent(walker_path.dentry);
935+
dput(walker_path.dentry);
936+
walker_path.dentry = parent_dentry;
924937
}
925-
parent_dentry = dget_parent(walker_path.dentry);
926-
dput(walker_path.dentry);
927-
walker_path.dentry = parent_dentry;
928938
}
929939
path_put(&walker_path);
930940

@@ -1022,6 +1032,9 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
10221032
* file. While walking from @dir to @mnt_root, we record all the domain's
10231033
* allowed accesses in @layer_masks_dom.
10241034
*
1035+
* Because of disconnected directories, this walk may not reach @mnt_dir. In
1036+
* this case, the walk will continue to @mnt_dir after this call.
1037+
*
10251038
* This is similar to is_access_to_paths_allowed() but much simpler because it
10261039
* only handles walking on the same mount point and only checks one set of
10271040
* accesses.
@@ -1063,8 +1076,11 @@ static bool collect_domain_accesses(
10631076
break;
10641077
}
10651078

1066-
/* We should not reach a root other than @mnt_root. */
1067-
if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir)))
1079+
/*
1080+
* Stops at the mount point or the filesystem root for a disconnected
1081+
* directory.
1082+
*/
1083+
if (dir == mnt_root || unlikely(IS_ROOT(dir)))
10681084
break;
10691085

10701086
parent_dentry = dget_parent(dir);

0 commit comments

Comments
 (0)