From aed5860e1b5e4726deb968d1da7e8274af962f53 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:50 +0200 Subject: [PATCH 01/11] ovl: handle idmapped mounts in ovl_create_object() and ovl_tmpfile() In preparation for allowing the overlay mount itself to be idmapped, thread the mount's struct mnt_idmap into the inode creation path and use it for inode_init_owner() instead of the hardcoded &nop_mnt_idmap. The preallocated overlay inode's i_{u,g}id are copied into the override credentials by ovl_override_creator_creds() to create the real upper inode, so honoring the overlay mount idmap here makes newly created files, directories, special files, symlinks and tmpfiles get the caller's mapped fs{u,g}id once overlay mounts can be idmapped. No functional change: until FS_ALLOW_IDMAP is set on ovl_fs_type the overlay mount idmap is always &nop_mnt_idmap, so inode_init_owner() behaves exactly as before. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-1-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/dir.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a033743dbf51..a7a393b04277 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -689,8 +689,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, return err; } -static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, - const char *link) +static int ovl_create_object(struct mnt_idmap *idmap, struct dentry *dentry, + int mode, dev_t rdev, const char *link) { int err; struct inode *inode; @@ -717,7 +717,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, inode_state_set(inode, I_CREATING); spin_unlock(&inode->i_lock); - inode_init_owner(&nop_mnt_idmap, inode, dentry->d_parent->d_inode, mode); + inode_init_owner(idmap, inode, dentry->d_parent->d_inode, mode); attr.mode = inode->i_mode; err = ovl_create_or_link(dentry, inode, &attr, false); @@ -734,13 +734,13 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) { - return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL); + return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL); } static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL)); + return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL)); } static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, @@ -750,13 +750,13 @@ static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, if (S_ISCHR(mode) && rdev == WHITEOUT_DEV) return -EPERM; - return ovl_create_object(dentry, mode, rdev, NULL); + return ovl_create_object(idmap, dentry, mode, rdev, NULL); } static int ovl_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *link) { - return ovl_create_object(dentry, S_IFLNK, 0, link); + return ovl_create_object(idmap, dentry, S_IFLNK, 0, link); } static int ovl_set_link_redirect(struct dentry *dentry) @@ -1444,7 +1444,7 @@ static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir, if (!inode) goto drop_write; - inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode_init_owner(idmap, inode, dir, mode); err = ovl_create_tmpfile(file, dentry, inode, inode->i_mode); if (err) goto put_inode; From 21b9aa3b8025445a71d5715458ea88457ca9e43f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:51 +0200 Subject: [PATCH 02/11] ovl: handle idmapped mounts in ovl_permission() When the overlay mount is idmapped, the permission check on the overlay inode must account for the mount's idmapping. Use the struct mnt_idmap passed in by the VFS instead of the hardcoded &nop_mnt_idmap when checking the overlay inode against the caller's credentials. The second check, which verifies that the mounter may access the underlying real inode, continues to use the real layer's idmap mnt_idmap(realpath.mnt) under the mounter's credentials and is deliberately left unchanged. The overlay mount idmap only affects how the caller views the overlay inode, not the mounter's access to the layers, so it cannot widen access to the real files. No functional change until FS_ALLOW_IDMAP is set on ovl_fs_type; until then the overlay mount idmap is always &nop_mnt_idmap. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-2-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 00c69707bda9..f59db57dfd55 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -306,7 +306,7 @@ int ovl_permission(struct mnt_idmap *idmap, * Check overlay inode with the creds of task and underlying inode * with creds of mounter */ - err = generic_permission(&nop_mnt_idmap, inode, mask); + err = generic_permission(idmap, inode, mask); if (err) return err; From 10a16f5b9111da6e553f8533605f7216e13a37b8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:52 +0200 Subject: [PATCH 03/11] ovl: handle idmapped mounts in ovl_setattr() Pass the mount's struct mnt_idmap to setattr_prepare() so that the permission checks for a chown/chmod performed through an idmapped overlay mount are evaluated in the mount's id space. The ownership requested in @attr is expressed relative to the overlay mount idmap. Before forwarding the change to the upper layer via ovl_do_notify_change() - whose notify_change() applies the upper layer idmap in turn - rebase ia_vfsuid/ia_vfsgid into the overlay's own id space, i.e. the same space as the overlay inode's i_{u,g}id established by ovl_copyattr(). Without this rebase the upper layer would interpret the caller's mount-relative id as an upper-relative one and store the wrong owner on disk, or reject it with -EOVERFLOW. from_vfsuid() returns INVALID_UID for an id that the overlay mount idmap does not map; that invalid id is carried faithfully into the forwarded iattr and rejected by the upper notify_change() via vfsuid_has_fsmapping(), so no bogus owner can be written. No functional change until FS_ALLOW_IDMAP is set on ovl_fs_type; until then the overlay mount idmap is &nop_mnt_idmap and from_vfsuid() is the identity. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-3-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/inode.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index f59db57dfd55..33734ca971e1 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -26,10 +26,18 @@ int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, bool full_copy_up = false; struct dentry *upperdentry; - err = setattr_prepare(&nop_mnt_idmap, dentry, attr); + err = setattr_prepare(idmap, dentry, attr); if (err) return err; + /* Rebase ownership from the mount idmap into overlay id space. */ + if (attr->ia_valid & ATTR_UID) + attr->ia_vfsuid = VFSUIDT_INIT(from_vfsuid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsuid)); + if (attr->ia_valid & ATTR_GID) + attr->ia_vfsgid = VFSGIDT_INIT(from_vfsgid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsgid)); + if (attr->ia_valid & ATTR_SIZE) { /* Truncate should trigger data copy up as well */ full_copy_up = true; From 22b27d403dd034148c8499c37dc1127e713f1b11 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:53 +0200 Subject: [PATCH 04/11] ovl: handle idmapped mounts in ovl_getattr() ovl_getattr() fetches attributes from the real (upper or lower) path via vfs_getattr(), so the returned stat->uid/stat->gid are already mapped through the real layer idmap but not through the overlay mount idmap. Unlike the generic path, the VFS does not re-apply the accessing mount's idmap after ->getattr returns - generic_fillattr() does it, but overlayfs bypasses it - so overlayfs has to do it itself. Map stat->uid/stat->gid through the overlay mount idmap before returning, mirroring generic_fillattr(). The owner reported through an idmapped overlay mount is thus the overlay-final id translated by the mount idmap. No functional change until FS_ALLOW_IDMAP is set on ovl_fs_type; until then make_vfsuid()/make_vfsgid() are the identity for &nop_mnt_idmap. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-4-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 33734ca971e1..45b2a5c3d978 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -180,6 +180,8 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, int fsid = 0; int err; bool metacopy_blocks = false; + vfsuid_t vfsuid; + vfsgid_t vfsgid; metacopy_blocks = ovl_is_metacopy_dentry(dentry); @@ -292,6 +294,12 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry))) stat->nlink = dentry->d_inode->i_nlink; + /* Map ownership of the real inode through the overlay mount idmap. */ + vfsuid = make_vfsuid(idmap, i_user_ns(inode), stat->uid); + vfsgid = make_vfsgid(idmap, i_user_ns(inode), stat->gid); + stat->uid = vfsuid_into_kuid(vfsuid); + stat->gid = vfsgid_into_kgid(vfsgid); + return err; } From d1f78a3fed4a949db70205719d4b17d97f5e95c0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:54 +0200 Subject: [PATCH 05/11] ovl: handle idmapped mounts in ovl_set_acl() The two checks ovl_set_acl() performs on the overlay inode itself - inode_owner_or_capable() and the setgid-stripping test - were done with &nop_mnt_idmap. On an idmapped overlay mount this compares the overlay inode's id space directly against the caller's: it denies the rightful owner (as seen through the mount idmap) the right to set an ACL, and evaluates the setgid-drop decision in the wrong id space, which can mis-set the mode on the upper inode. Use the struct mnt_idmap passed in by the VFS for both. Fold the open-coded "caller not in group and not CAP_FSETID privileged" test into in_group_or_capable() with i_gid_into_vfsgid(), the same idmap-aware helpers used by setattr_should_drop_sgid(). The subsequent internal forced setgid-kill via ovl_setattr() stays on &nop_mnt_idmap: it carries only ATTR_KILL_SGID with no uid/gid to translate and is overlayfs' own mode change, not a user-driven operation through the mount. No functional change until FS_ALLOW_IDMAP is set on ovl_fs_type. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-5-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/inode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 45b2a5c3d978..57aa74de5bc4 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -550,7 +550,7 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return -EOPNOTSUPP; if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) return acl ? -EACCES : 0; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(idmap, inode)) return -EPERM; /* @@ -558,8 +558,8 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * be done with mounter's capabilities and so that won't do it for us). */ if (unlikely(inode->i_mode & S_ISGID) && type == ACL_TYPE_ACCESS && - !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&nop_mnt_idmap, inode, CAP_FSETID)) { + !in_group_or_capable(idmap, inode, + i_gid_into_vfsgid(idmap, inode))) { struct iattr iattr = { .ia_valid = ATTR_KILL_SGID }; err = ovl_setattr(&nop_mnt_idmap, dentry, &iattr); From adedb6a00a1e4c77b1de9ea1f63b5008ad0c21f5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:55 +0200 Subject: [PATCH 06/11] ovl: allow idmapping overlay mounts Now that every overlay inode operation honors the overlay mount idmap, allow the merged overlay mount itself to be idmapped by setting FS_ALLOW_IDMAP on ovl_fs_type. mount_setattr(MOUNT_ATTR_IDMAP) can then apply an idmapping to an overlay mount, exposing the merged tree under a different ownership view. The composition is clean because overlayfs already normalizes every underlying id through the relevant layer idmap when it copies attributes into the overlay inode (ovl_copyattr()); the overlay inode's i_{u,g}id are thus "overlay-final" ids. The overlay mount idmap composes on top of that pivot: it is applied to (getattr) and removed from (setattr, create) those ids at the overlay-inode boundary - permission, getattr, setattr, ACL owner checks and inode_init_owner() - while the underlying layers keep being accessed with the mounter's credentials through their own, possibly idmapped, mounts. The mount idmap therefore only changes how the caller sees the overlay inode and never widens the mounter's access to the layers. This is deliberately the final code patch of the series: only once every operation honors the mount idmap is it safe to make overlay mounts idmappable. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-6-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 60f0b7ceef0a..f2889cf9bc07 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1573,7 +1573,7 @@ struct file_system_type ovl_fs_type = { .name = "overlay", .init_fs_context = ovl_init_fs_context, .parameters = ovl_parameter_spec, - .fs_flags = FS_USERNS_MOUNT, + .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP, .kill_sb = kill_anon_super, }; MODULE_ALIAS_FS("overlay"); From 18a76ce66f3a862f16c73109cefe3cc0a6f1f214 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:56 +0200 Subject: [PATCH 07/11] docs: document idmapped overlay mounts Describe that the merged overlay mount itself can be turned into an idmapped mount with mount_setattr(2), how the overlay mount idmapping composes with any layer idmappings, and that the mounter's access to the underlying layers is unaffected. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-7-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/overlayfs.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst index eb846518e6ac..1a29a5afabd7 100644 --- a/Documentation/filesystems/overlayfs.rst +++ b/Documentation/filesystems/overlayfs.rst @@ -347,6 +347,22 @@ The resulting access permissions should be the same. The difference is in the time of copy (on-demand vs. up-front). +Idmapped mounts +--------------- + +The overlay mount itself can be turned into an idmapped mount by applying an +idmapping to it with mount_setattr(2) and MOUNT_ATTR_IDMAP, just like for +other filesystems that support idmapped mounts. + +The mount idmapping only changes how ownership and permissions of the overlay +inodes are presented to and interpreted for the caller. It does not change +how overlayfs accesses the underlying layers: those are still accessed with +the stashed mounter's credentials through their own mounts, which may +themselves be idmapped. The overlay mount idmapping and any layer idmapping +compose, an underlying id is first mapped according to the relevant layer +idmapping and then according to the overlay mount idmapping. + + Multiple lower layers --------------------- From e5804b1024193c91ff2648403beda882cb4f8b6c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:57 +0200 Subject: [PATCH 08/11] selftests/filesystems/overlayfs: fix set_layers_via_fds link error set_layers_via_fds.c calls open_tree() directly, but that wrapper is not provided by all C libraries, so the test fails to link with "undefined reference to open_tree" on toolchains without it. Use sys_open_tree() from ../wrappers.h, the syscall wrapper already used for move_mount(), fsmount() and friends here. This is also a prerequisite for building the new idmapped_mounts test added to this directory. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-8-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/overlayfs/set_layers_via_fds.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c index 3c0b93183348..7a293544233d 100644 --- a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c +++ b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c @@ -624,7 +624,7 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) ASSERT_EQ(sys_move_mount(fd_tmpfs, "", -EBADF, "/set_layers_via_fds_tmpfs", MOVE_MOUNT_F_EMPTY_PATH), 0); - fd_tmp = open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + fd_tmp = sys_open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(fd_tmp, 0); layer_fds[0] = openat(fd_tmp, "upper", O_CLOEXEC | O_DIRECTORY | O_PATH); @@ -633,25 +633,25 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) layer_fds[1] = openat(fd_tmp, "work", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_GE(layer_fds[1], 0); - layer_fds[2] = open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[2] = sys_open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[2], 0); - layer_fds[3] = open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[3] = sys_open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[3], 0); - layer_fds[4] = open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[4] = sys_open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[4], 0); - layer_fds[5] = open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[5] = sys_open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[5], 0); - layer_fds[6] = open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[6] = sys_open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[6], 0); - layer_fds[7] = open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[7] = sys_open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[7], 0); - layer_fds[8] = open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[8] = sys_open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[8], 0); ASSERT_EQ(close(fd_tmpfs), 0); From 1ec284f38b9f6d60c1e2c935a37bd6484cd2d14d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:58 +0200 Subject: [PATCH 09/11] selftests/filesystems/overlayfs: test idmapped overlay mounts Add a selftest for idmapping the merged overlay mount itself. It applies an idmapping to a freshly created (still detached) overlay mount with mount_setattr(MOUNT_ATTR_IDMAP) and checks that: - getattr reports ownership mapped through the mount idmap; - a file created through the idmapped mount is stored on the upper layer with the corresponding overlay-final id; - chown through the idmapped mount round-trips; - an nfs_export overlay can be idmapped and decodable file handles round-trip through it with correctly mapped ownership. The layers live on a private tmpfs and are owned by the host id so that they map to id 0 through the test idmapping, allowing the root caller to operate on them. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-9-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/overlayfs/.gitignore | 1 + .../selftests/filesystems/overlayfs/Makefile | 2 + .../filesystems/overlayfs/idmapped_mounts.c | 501 ++++++++++++++++++ 3 files changed, 504 insertions(+) create mode 100644 tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore index e23a18c8b37f..077f7a128168 100644 --- a/tools/testing/selftests/filesystems/overlayfs/.gitignore +++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only dev_in_maps set_layers_via_fds +idmapped_mounts diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index d3ad4a77db9b..b3185f684add 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -8,7 +8,9 @@ LOCAL_HDRS += ../wrappers.h log.h TEST_GEN_PROGS := dev_in_maps TEST_GEN_PROGS += set_layers_via_fds +TEST_GEN_PROGS += idmapped_mounts include ../../lib.mk $(OUTPUT)/set_layers_via_fds: ../utils.c +$(OUTPUT)/idmapped_mounts: ../utils.c diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c new file mode 100644 index 000000000000..44a75839f4ed --- /dev/null +++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kselftest_harness.h" +#include "../wrappers.h" +#include "../utils.h" + +/* + * An idmapping that maps the mount-visible id range [0, ID_RANGE) onto the + * host/overlay-final id range [ID_HOST, ID_HOST + ID_RANGE). Through such an + * idmapped overlay mount, an overlay-final id of ID_HOST + n is reported as n, + * and an id of n requested through the mount is stored as ID_HOST + n. + */ +#define ID_NS 0 +#define ID_HOST 10000 +#define ID_RANGE 10000 + +/* + * For the composition test the lower layer's on-disk ids live in a + * separate range and are mapped by an idmapped lower layer onto the + * overlay-final range [ID_HOST, ID_HOST + ID_RANGE). + */ +#define LAYER_HOST 20000 + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +static inline int sys_mount_setattr(int dfd, const char *path, + unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static bool ovl_supported(void) +{ + int fd = sys_fsopen("overlay", 0); + + if (fd < 0) + return false; + close(fd); + return true; +} + +/* base/{l,u,w} owned by ID_HOST so they map to ID_NS through the idmap. */ +static int setup_layers(const char *base) +{ + static const char *sub[] = { "", "/l", "/u", "/w" }; + char path[PATH_MAX]; + + for (size_t i = 0; i < ARRAY_SIZE(sub); i++) { + snprintf(path, sizeof(path), "%s%s", base, sub[i]); + if (mkdir(path, 0755) && errno != EEXIST) + return -1; + if (i && chown(path, ID_HOST, ID_HOST)) + return -1; + } + return 0; +} + +static int ovl_mount(const char *base, bool nfs_export) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX]; + int fsfd, ovl; + + snprintf(lower, sizeof(lower), "%s/l", base); + snprintf(upper, sizeof(upper), "%s/u", base); + snprintf(work, sizeof(work), "%s/w", base); + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", lower, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0)) + goto err; + if (nfs_export && + (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", "on", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export", "on", 0))) + goto err; + if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* Idmap the (still detached, not yet visible) overlay mount @mfd. */ +static int ovl_idmap(int mfd) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int ret, userns_fd; + + /* + * get_userns_fd(fs_id, mount_id, range): a file whose filesystem id + * is fs_id + n is shown through the idmapped mount as mount_id + n. + * Here the overlay-final (fs side) range is [ID_HOST, ..) and the + * caller-visible (mount side) range is [ID_NS, ..). + */ + userns_fd = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (userns_fd < 0) + return -1; + + attr.userns_fd = userns_fd; + ret = sys_mount_setattr(mfd, "", AT_EMPTY_PATH, &attr, sizeof(attr)); + close(userns_fd); + return ret; +} + +/* Clone @path into a detached, idmapped mount usable as an overlay layer. */ +static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fd_tree, userns_fd; + + fd_tree = sys_open_tree(AT_FDCWD, path, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd_tree < 0) + return -1; + userns_fd = get_userns_fd(nsid, hostid, range); + if (userns_fd < 0) { + close(fd_tree); + return -1; + } + attr.userns_fd = userns_fd; + if (sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(userns_fd); + close(fd_tree); + return -1; + } + close(userns_fd); + return fd_tree; +} + +/* Overlay with a layer passed by fd (idmapped) plus a plain upper/work. */ +static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower) +{ + int fsfd, ovl; + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower) || + sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* + * Mount an overlay inside user namespace @u1 (so the overlay sb's s_user_ns is + * not the initial namespace) and idmap that overlay mount with @u2. Runs in a + * child that joins @u1; returns 0 on success. + */ +static int userns_overlay_child(int u1) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + struct stat st; + int ovl, u2; + + /* Become root in the overlay sb's user namespace u1. */ + if (!switch_userns(u1, 0, 0, false)) + return fprintf(stderr, "userns: switch_userns: %m\n"), -1; + if (unshare(CLONE_NEWNS) || + sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL)) + return fprintf(stderr, "userns: unshare/slave: %m\n"), -1; + if (sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) + return fprintf(stderr, "userns: mount tmpfs: %m\n"), -1; + if (setup_layers("/tmp/ovl")) + return fprintf(stderr, "userns: setup_layers: %m\n"), -1; + if (mknod("/tmp/ovl/l/file", S_IFREG | 0644, 0) || + chown("/tmp/ovl/l/file", ID_HOST + 5, ID_HOST + 5)) + return fprintf(stderr, "userns: lower file: %m\n"), -1; + + ovl = ovl_mount("/tmp/ovl", false); + if (ovl < 0) + return fprintf(stderr, "userns: ovl_mount: %m\n"), -1; + + /* + * mount_setattr() requires CAP_SYS_ADMIN over the idmap user + * namespace, so it must be a child of u1. Create it now, from + * inside u1. + */ + u2 = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (u2 < 0) + return fprintf(stderr, "userns: get_userns_fd: %m\n"), -1; + attr.userns_fd = u2; + if (sys_mount_setattr(ovl, "", AT_EMPTY_PATH, &attr, sizeof(attr))) + return fprintf(stderr, "userns: mount_setattr: %m\n"), -1; + close(u2); + + if (fstatat(ovl, "file", &st, 0)) + return fprintf(stderr, "userns: fstatat: %m\n"), -1; + if (st.st_uid != ID_NS + 5 || st.st_gid != ID_NS + 5) { + fprintf(stderr, "userns: got %u:%u expected %u:%u\n", + st.st_uid, st.st_gid, ID_NS + 5, ID_NS + 5); + return -1; + } + return 0; +} + +FIXTURE(idmapped_overlay) { + char base[64]; +}; + +FIXTURE_SETUP(idmapped_overlay) +{ + /* Private mount namespace so test mounts need no cleanup. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); + + /* tmpfs for the layers so we can chown them to arbitrary ids. */ + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/ovl"); + ASSERT_EQ(setup_layers(self->base), 0); +} + +FIXTURE_TEARDOWN(idmapped_overlay) +{ +} + +/* A file owned by ID_HOST + 5 is reported as ID_NS + 5 through the idmap. */ +TEST_F(idmapped_overlay, getattr) +{ + char path[PATH_MAX]; + struct stat st; + int ovl; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 5, ID_HOST + 5), 0); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Every creation path initializes the new owner through the mount idmap: + * created as caller id ID_NS, stored on the upper layer as overlay-final + * ID_HOST. Covers ovl_create() (regular file), ovl_mkdir(), ovl_mknod() + * and ovl_symlink() (which share ovl_create_object()), plus the separate + * ovl_tmpfile() path. + */ +TEST_F(idmapped_overlay, create) +{ + static const char *names[] = { "reg", "dir", "fifo", "lnk" }; + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* One object per creation operation, all as caller id ID_NS. */ + fd = openat(ovl, "reg", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + ASSERT_EQ(mkdirat(ovl, "dir", 0755), 0); + ASSERT_EQ(mknodat(ovl, "fifo", S_IFIFO | 0644, 0), 0); + ASSERT_EQ(symlinkat("target", ovl, "lnk"), 0); + + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + /* Reported as ID_NS through the idmapped mount ... */ + ASSERT_EQ(fstatat(ovl, names[i], &st, AT_SYMLINK_NOFOLLOW), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* ... and stored as ID_HOST on the upper layer. */ + snprintf(path, sizeof(path), "%s/u/%s", self->base, names[i]); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + } + + /* O_TMPFILE goes through the separate ovl_tmpfile() path. */ + fd = openat(ovl, ".", O_TMPFILE | O_WRONLY, 0644); + ASSERT_GE(fd, 0); + /* Inside the mount: caller id ID_NS. */ + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* Link it in so the upper backing file can be inspected too. */ + ASSERT_EQ(linkat(fd, "", ovl, "tmp", AT_EMPTY_PATH), 0); + EXPECT_EQ(close(fd), 0); + snprintf(path, sizeof(path), "%s/u/tmp", self->base); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + + EXPECT_EQ(close(ovl), 0); +} + +/* chown through the idmapped mount round-trips: ID_NS + 5 <-> ID_HOST + 5. */ +TEST_F(idmapped_overlay, chown) +{ + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + fd = openat(ovl, "f", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(fchownat(ovl, "f", ID_NS + 5, ID_NS + 5, 0), 0); + + ASSERT_EQ(fstatat(ovl, "f", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + snprintf(path, sizeof(path), "%s/u/f", self->base); + ASSERT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST + 5); + EXPECT_EQ(st.st_gid, ID_HOST + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Composition: an idmapped lower layer underneath an idmapped overlay mount. + * An on-disk id is mapped by the layer idmap into the overlay-final range and + * then by the mount idmap into the caller's range: + * + * on-disk LAYER_HOST+7 --layer--> ID_HOST+7 --mount--> ID_NS+7 + */ +TEST_F(idmapped_overlay, composition) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX], path[PATH_MAX]; + struct stat st; + int ovl, fd_lower; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(lower, sizeof(lower), "%s/l", self->base); + snprintf(upper, sizeof(upper), "%s/u", self->base); + snprintf(work, sizeof(work), "%s/w", self->base); + + /* Put the lower layer's ids in the on-disk [LAYER_HOST, ..) range. */ + ASSERT_EQ(chown(lower, LAYER_HOST, LAYER_HOST), 0); + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, LAYER_HOST + 7, LAYER_HOST + 7), 0); + + /* Idmapped lower: on-disk LAYER_HOST <-> overlay-final ID_HOST. */ + fd_lower = idmapped_layer_fd(lower, LAYER_HOST, ID_HOST, ID_RANGE); + ASSERT_GE(fd_lower, 0); + + ovl = ovl_mount_lower_fd(upper, work, fd_lower); + ASSERT_GE(ovl, 0); + EXPECT_EQ(close(fd_lower), 0); + + /* Idmap the overlay mount: overlay-final ID_HOST <-> caller ID_NS. */ + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(ovl), 0); +} + +/* An idmapped overlay mount whose sb lives inside a user namespace. */ +TEST_F(idmapped_overlay, userns) +{ + int u1; + pid_t pid; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + /* u1 backs the overlay sb: identity-mapped, but not the init ns. */ + u1 = get_userns_fd(0, 0, 65536); + if (u1 < 0) + SKIP(return, "user namespaces not available"); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + int ret = userns_overlay_child(u1); + + _exit(ret ? EXIT_FAILURE : EXIT_SUCCESS); + } + EXPECT_EQ(wait_for_pid(pid), 0); + + EXPECT_EQ(close(u1), 0); +} + +/* + * An nfs_export overlay can be idmapped, and decodable file handles round-trip + * through the idmapped mount with correctly mapped ownership. Overlay file + * handles encode object identity, not ownership, so the mount idmap does not + * affect them; it only maps the owner reported once a handle is reopened. + */ +TEST_F(idmapped_overlay, nfs_export_handles) +{ + char path[PATH_MAX], mnt[128]; + union { + struct file_handle fh; + char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ]; + } fhu; + struct file_handle *fh = &fhu.fh; + struct stat st; + int ovl, mfd, fd, mount_id; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0); + + /* nfs_export=on gives decodable overlay file handles. */ + ovl = ovl_mount(self->base, true); + if (ovl < 0) + SKIP(return, "overlayfs nfs_export not supported"); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* Attach the idmapped mount so handles can be resolved against it. */ + snprintf(mnt, sizeof(mnt), "%s/mnt", self->base); + ASSERT_EQ(mkdir(mnt, 0755), 0); + ASSERT_EQ(sys_move_mount(ovl, "", AT_FDCWD, mnt, + MOVE_MOUNT_F_EMPTY_PATH), 0); + + snprintf(path, sizeof(path), "%s/file", mnt); + fh->handle_bytes = MAX_HANDLE_SZ; + ASSERT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), 0); + + mfd = open(mnt, O_RDONLY | O_DIRECTORY); + ASSERT_GE(mfd, 0); + fd = open_by_handle_at(mfd, fh, O_RDONLY); + EXPECT_EQ(close(mfd), 0); + ASSERT_GE(fd, 0); + + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(close(ovl), 0); +} + +TEST_HARNESS_MAIN From 7289a359e627ce27f66a1e1b1d956c690cb10f9e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 15 Jun 2026 15:19:59 +0200 Subject: [PATCH 10/11] ovl: document security.capability idmapping on the xattr forward paths Now that an overlay mount can itself be idmapped, every id exposed at the overlay-inode boundary is mapped through the overlay mount idmap. security.capability is the one id-bearing xattr that overlayfs does not translate in its own boundary code: the embedded rootid of a v3 fscap is instead mapped by the capability LSM inside vfs_getxattr() and vfs_setxattr(). It still composes correctly only because the xattr read and write forwards go through the security-aware vfs_getxattr() / vfs_setxattr() rather than the raw __vfs_*xattr() variants: commoncap maps the rootid through the layer idmap at the overlay-to-real forward, while the overlay mount idmap is applied by the outer vfs_getxattr() at the syscall boundary, mirroring the layer-then-mount composition used by ovl_getattr(). The raw __vfs_*xattr() variants skip the security hooks and would silently drop the rootid mapping. Comment both forwards - the read in ovl_xattr_get() and the write in ovl_do_setxattr() - so they are not converted to them by accident. No functional change. Link: https://patch.msgid.link/20260615-work-idmapped-overlayfs-v1-10-7381632aa402@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/overlayfs.h | 1 + fs/overlayfs/xattrs.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index b75df37f70ac..e0d8c6152e9f 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -320,6 +320,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { + /* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */ int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), dentry, name, value, size, flags); diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c index aa95855c7023..811c94d2d9e9 100644 --- a/fs/overlayfs/xattrs.c +++ b/fs/overlayfs/xattrs.c @@ -84,6 +84,7 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char struct path realpath; ovl_i_path_real(inode, &realpath); + /* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */ with_ovl_creds(dentry->d_sb) return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size); } From 077ab8985ee278c3d8618182d335b0f0cd919e16 Mon Sep 17 00:00:00 2001 From: Vivek Parikh Date: Wed, 5 Aug 2026 10:27:54 +0530 Subject: [PATCH 11/11] ovl: fix double end_creating() on the casefold-mismatch path ovl_create_real() releases the new dentry twice when the casefold consistency check fails. The S_IFDIR branch calls end_creating() and sets err, then falls through to the common out: label which calls end_creating() on the same dentry again: case S_IFDIR: newdentry = ovl_do_mkdir(ofs, dir, newdentry, attr->mode); err = PTR_ERR_OR_ZERO(newdentry); if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) { pr_warn_ratelimited(...); end_creating(newdentry); /* first */ err = -EINVAL; } break; ... if (err) goto out; ... out: if (err) { end_creating(newdentry); /* second, same dentry */ return ERR_PTR(err); } end_creating() is end_dirop(), which does inode_unlock() on the parent and dput() on the dentry, so the parent directory's i_rwsem is unlocked twice and the dentry is put twice. The second unlock releases a lock that is not held, which is what wedges every later creation under that parent, and the second dput() drops a reference that was never taken. The branch was added by commit dfc7da402ccc ("ovl: Check for casefold consistency when creating new dentries") as a bare dput(), which already released the reference twice; commit fe497f0759e0 ("VFS: change vfs_mkdir() to unlock on failure.") converted both sites to end_creating(), adding the double unlock. This is reachable by an unprivileged user. The casefold consistency of the layers is validated at mount time in ovl_parse_layer(), and again on every lookup in ovl_lookup_single(), but ofs->workdir is the internal "work" subdirectory created inside the user-supplied workdir, and that subdirectory is not re-checked. Marking it casefolded after the mount therefore makes every ovl_create_temp() inherit the wrong state - and that path reaches ovl_create_real() through ovl_start_creating_temp(), which uses start_creating() with a generated name and so never runs the lookup-time check. unshare -Urm mount -t tmpfs -o casefold=utf8-12.1.0 tmpfs mnt mkdir -p mnt/lower/d mnt/upper mnt/work mnt/merged mount -t overlay ovl -o lowerdir=mnt/lower,\ upperdir=mnt/upper,workdir=mnt/work mnt/merged chattr +F mnt/work/work mkdir mnt/merged/d/sub # directory copy-up overlayfs: wrong inherited casefold (work/#5) and the next copy-up blocks forever on the parent's i_rwsem: mkdir D start_creating+0x65/0xb0 ovl_start_creating_temp+0xb0/0xe0 [overlay] ovl_create_temp+0xa3/0x1d0 [overlay] ovl_copy_up_one+0x1f1c/0x21c0 [overlay] ovl_copy_up_flags+0xf5/0x140 [overlay] ovl_create_object+0xb7/0x220 [overlay] ovl_mkdir+0x23/0x40 [overlay] Drop the end_creating() from the branch and let out: own the cleanup, which is what every other error path in this function already does. Fixes: dfc7da402ccc ("ovl: Check for casefold consistency when creating new dentries") Cc: stable@vger.kernel.org Signed-off-by: Vivek Parikh Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/dir.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a7a393b04277..3cd8194cbb1d 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -188,7 +188,6 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent, if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) { pr_warn_ratelimited("wrong inherited casefold (%pd2)\n", newdentry); - end_creating(newdentry); err = -EINVAL; } break;