From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794585740; b=CS3+PpilqXC0D6kTeHgrHJlewYiQe11b/V51b5mEHTGbYmhQoEt8tv5qp5XnxM2yJbe4 uMoYkoDC+flCKqTcuRLA9ltbYwn290lxcwes/ipT/lao6KGoVxHk4V3G+0lgxDjwaI2su QvPqG2UI0Ayo22IBPFMfcUDgzTi/Kc6DQJ5M1CGyZjurGjQ6JpPK4Sf36BMzYG+Ww3dS6 uINA+nTIYkOA8l+4npUS5icHK8z1VRIfmKaLiiNTeGZQDyToy1wb0vs8Dlz+RB5Oj5CCZ atUzb/Z9P5jk1xpl7xQ0LzoxcjV8NRovjWXsxn3EFSqfgnSQA/yMpE+ZJHGOLT5m27w== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794585740; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:User-Agent:X-stable:X-Patchwork-Hint: MIME-Version:Content-Transfer-Encoding; bh=rsMaSMOWxQGKxNkexoorD5DcI0l2F47rCvkRXrreOpc=; b=K0ADP/JsK+MKaI3A6ErJvALkXSjeLVNjhylS+mMG5k54dkEWfpm+HNrB9hLZ2NrnZmrb laVI16lNnXkCwcEescbzD2Q93NM/kHYaMJC2efj+nYRTpzQF6gQb4xHnbURxPZesrLDe/ 3D5O5DZOtq2AQE5x8JIUE+Sj5/dqEOpjxuf7z6zrpt9jz0itWnOgk00//+CnRHz4uY73H mOSRxwNXtLE9I9knv6V8zc4uRe9LgY3nDKNl7V7BteG85/f6GaghN1cR87WQ6BJNN91fw /cqh6Dhol4XP9KIKOsgfNlsYQbLOSYrgd+tnWr6y2JFdRGOCsZDape7MWbTkDkO4w4w== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=dodGjllV; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 33C6A606A336; Fri, 13 Nov 2026 16:02:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=dodGjllV Received: by smtp.kernel.org (Postfix) with ESMTPSA id CAC651F000CB; Fri, 13 Nov 2026 16:02:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1794585740; bh=rsMaSMOWxQGKxNkexoorD5DcI0l2F47rCvkRXrreOpc=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=dodGjllVYF1+ptMzeO7YedMa0HZJF4KP/xkieOkoawCRFP5rsgLkQjI6kb2wIwLQZ YCQHh6v1jBV81O6VuTFsLR9xjKTlRkNapGz1OLwuV/wpacIjwJB/CBVFisGr0rFOJn qZBXZbTmN7I03cB1pQs1xuEzDIUroseB/ftyfKvM= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Chris Mason , Hungyu Lin , Amir Goldstein , Sasha Levin Subject: [PATCH 7.3 339/507] ovl: fix whiteout creation in casefolded directories Date: Fri, 13 Nov 2026 10:50:43 -0500 Message-ID: <20261113153806.893423330@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261113153753.358973689@linuxfoundation.org> References: <20261113153753.358973689@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 7.3-stable review patch. If anyone has any objections, please let me know. ------------------ From: Chris Mason [ Upstream commit e803c82dcdd15e87b48fca1ea8d2cfe6a7ed83d4 ] unlink and rmdir fail with EINVAL on an overlay with casefolded (case-insensitive) layers, if the name exists in a lower layer. Casefolded layers need CONFIG_UNICODE. Root can mount such an overlay, and so can an unprivileged user in a user namespace, with tmpfs layers. Overlayfs does not change a lower layer. To remove a name that exists there, it hides the name with a marker file, a whiteout. It creates the whiteout in the work directory, a scratch directory on the upper filesystem, and renames it over the name in the upper directory: - ovl_whiteout() creates the whiteout with mknod, or as a hard link to an earlier whiteout. - ovl_cleanup_and_whiteout() then prepares the rename with start_renaming_two_dentries(), which returns -EINVAL if the source or the target dentry is unhashed, that is, not in the dentry hash table. With casefolded directories, both dentries can be unhashed: - The target. Lookup in a casefolded directory leaves a negative dentry unhashed, so the rename target is unhashed if the name does not exist in the upper directory. - The source, which is the new whiteout. tmpfs hashes a dentry when mknod or link makes it positive, but ext4 does not, so on ext4 the source is unhashed as well. The cleanup after the failure also refuses an unhashed dentry, so on ext4 the whiteout is left behind in the work directory. Reproduced with rm and rmdir on casefolded tmpfs and ext4 layers, also as uid 1000 in a user namespace. Fix this by creating the whiteout with ovl_create_temp(), which repeats the lookup if the new dentry is unhashed. Prepare the rename with start_renaming_dentry(), which looks up the target name with the directories locked. The caller looked the name up without a lock, so return -ESTALE if the name now refers to a different inode. Fixes: 833d2b3a072f ("Add start_renaming_two_dentries()") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason [amir: pass the name used for lookup instead of taking a name snapshot] Tested-by: Hungyu Lin Signed-off-by: Amir Goldstein Signed-off-by: Sasha Levin --- fs/overlayfs/dir.c | 46 ++++++++++++++++++++++------------------ fs/overlayfs/overlayfs.h | 10 +-------- fs/overlayfs/readdir.c | 3 ++- fs/overlayfs/util.c | 2 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index f716ac13badf3..c77af9a79d883 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -74,39 +74,34 @@ static struct dentry *ovl_start_creating_temp(struct ovl_fs *ofs, &QSTR(name)); } +/* + * The whiteout is the source of a rename, so it has to be hashed. + * ovl_create_temp() takes care of that for a casefolded workdir. + */ static struct dentry *ovl_whiteout(struct ovl_fs *ofs) { int err; struct dentry *whiteout, *link; struct dentry *workdir = ofs->workdir; - struct inode *wdir = workdir->d_inode; - char name[OVL_TEMPNAME_SIZE]; guard(mutex)(&ofs->whiteout_lock); if (!ofs->whiteout) { - whiteout = ovl_start_creating_temp(ofs, workdir, name); + whiteout = ovl_create_temp(ofs, workdir, &(struct ovl_cattr) { + .mode = S_IFCHR | WHITEOUT_MODE, + .rdev = WHITEOUT_DEV }); if (IS_ERR(whiteout)) return whiteout; - err = ovl_do_whiteout(ofs, wdir, whiteout); - if (!err) - ofs->whiteout = dget(whiteout); - end_creating(whiteout); - if (err) - return ERR_PTR(err); + ofs->whiteout = whiteout; } if (!ofs->no_shared_whiteout) { - link = ovl_start_creating_temp(ofs, workdir, name); - if (IS_ERR(link)) + link = ovl_create_temp(ofs, workdir, &(struct ovl_cattr) { + .hardlink = ofs->whiteout }); + if (!IS_ERR(link)) return link; - err = ovl_do_link(ofs, ofs->whiteout, wdir, link); - if (!err) - whiteout = dget(link); - end_creating(link); - if (!err) - return whiteout; + err = PTR_ERR(link); if (err != -EMLINK) { pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%u)\n", ofs->whiteout->d_inode->i_nlink, @@ -120,9 +115,10 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs) } int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir, - struct dentry *dentry) + struct dentry *dentry, const struct qstr *name) { struct dentry *whiteout; + struct qstr last = QSTR_LEN(name->name, name->len); struct renamedata rd = {}; int err; int flags = 0; @@ -139,9 +135,17 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir, rd.old_parent = ofs->workdir; rd.new_parent = dir; rd.flags = flags; - err = start_renaming_two_dentries(&rd, whiteout, dentry); + /* + * The caller looked up @dentry by @name without a lock on @dir, and a + * casefolded directory leaves a negative dentry unhashed. Nothing tells + * if such a dentry is still valid, so look up @name again with @dir locked. + */ + err = start_renaming_dentry(&rd, 0, whiteout, &last); if (!err) { - err = ovl_do_rename_rd(&rd); + if (d_inode(rd.new_dentry) != d_inode(dentry)) + err = -ESTALE; + else + err = ovl_do_rename_rd(&rd); end_renaming(&rd); } if (err) @@ -847,7 +851,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, goto out_dput_upper; } - err = ovl_cleanup_and_whiteout(ofs, upperdir, upper); + err = ovl_cleanup_and_whiteout(ofs, upperdir, upper, &dentry->d_name); if (!err) ovl_dir_modified(dentry->d_parent, true); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 91cdd7e15696c..4faebb4c6825b 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -394,14 +394,6 @@ static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir, return ovl_do_rename_rd(&rd); } -static inline int ovl_do_whiteout(struct ovl_fs *ofs, - struct inode *dir, struct dentry *dentry) -{ - int err = vfs_whiteout(ovl_upper_mnt_idmap(ofs), dir, dentry); - pr_debug("whiteout(%pd2) = %i\n", dentry, err); - return err; -} - static inline struct file *ovl_do_tmpfile(struct ovl_fs *ofs, struct dentry *dentry, umode_t mode) { @@ -897,7 +889,7 @@ static inline void ovl_copyflags(struct inode *from, struct inode *to) /* dir.c */ extern const struct inode_operations ovl_dir_inode_operations; int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir, - struct dentry *dentry); + struct dentry *dentry, const struct qstr *name); struct ovl_cattr { dev_t rdev; umode_t mode; diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 80366424b3dc1..aefa41b163cd8 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -1322,7 +1322,8 @@ int ovl_indexdir_cleanup(struct ovl_fs *ofs) * Whiteout orphan index to block future open by * handle after overlay nlink dropped to zero. */ - err = ovl_cleanup_and_whiteout(ofs, indexdir, index); + err = ovl_cleanup_and_whiteout(ofs, indexdir, index, + &QSTR_LEN(p->name, p->len)); } else { /* Cleanup orphan index entries */ err = ovl_cleanup(ofs, indexdir, index); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index b41f4788e4f06..47734eeb97b75 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -1118,7 +1118,7 @@ static void ovl_cleanup_index(struct dentry *dentry) } else if (ovl_index_all(dentry->d_sb)) { /* Whiteout orphan index to block future open by handle */ err = ovl_cleanup_and_whiteout(OVL_FS(dentry->d_sb), - indexdir, index); + indexdir, index, &name); } else { /* Cleanup orphan index entries */ err = ovl_cleanup(ofs, indexdir, index); -- 2.53.0