From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Rochan Avlur <rochan.avlur@gmail.com>,
Yuezhang Mo <Yuezhang.Mo@sony.com>,
Namjae Jeon <linkinjeon@kernel.org>,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.6.y 6/6] exfat: preserve benign secondary entries during rename and move
Date: Tue, 21 Jul 2026 07:47:43 -0400 [thread overview]
Message-ID: <20260721114743.3689685-6-sashal@kernel.org> (raw)
In-Reply-To: <20260721114743.3689685-1-sashal@kernel.org>
From: Rochan Avlur <rochan.avlur@gmail.com>
[ Upstream commit 942296784b2a9439651750c42f540bf2579b330f ]
Commit 8258ef28001a ("exfat: handle unreconized benign secondary
entries") added cluster freeing for benign secondary entries inside
exfat_remove_entries(). However, exfat_remove_entries() is also called
from the rename and move paths (exfat_rename_file and exfat_move_file),
where the old entry set is being relocated rather than deleted. This
causes benign secondary entries such as vendor extension entries to be
silently destroyed on rename or cross-directory move, violating the
exFAT spec requirement (section 8.2) that implementations preserve
unrecognized benign secondary entries.
Fix this by adding a free_benign parameter to exfat_remove_entries()
so callers can suppress cluster freeing during relocation, and
extending exfat_init_ext_entry() to copy trailing benign secondary
entries from the old entry set into the new one internally. Also
clean up the error paths to delete newly allocated entries on failure.
Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/
Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/dir.c | 45 +++++++++--
fs/exfat/exfat_fs.h | 5 +-
fs/exfat/namei.c | 191 +++++++++++++++++++++++++++++---------------
3 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 6c714b110f089b..2c4afa6675f55a 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -552,21 +552,43 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,
exfat_free_cluster(inode, &dir);
}
+/*
+ * exfat_init_ext_entry - initialize extension entries of a dentry set
+ * @inode: parent directory inode
+ * @p_dir: directory the dentry set is in
+ * @entry: index of the first entry of the dentry set
+ * @num_entries: number of entries excluding benign secondary entries
+ * @p_uniname: filename to store
+ * @old_es: optional source entry set with benign secondary entries, or NULL
+ * @num_extra: number of benign secondary entries to copy from @old_es
+ *
+ * Set up the file, stream extension, and filename entries, optionally
+ * preserving @num_extra benign secondary entries from @old_es. @old_es
+ * may refer to the same dentry set that is being initialized.
+ */
int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
- int entry, int num_entries, struct exfat_uni_name *p_uniname)
+ int entry, int num_entries, struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra)
{
struct super_block *sb = inode->i_sb;
- int i;
+ int i, src_start = 0;
unsigned short *uniname = p_uniname->name;
struct exfat_dentry *ep;
struct buffer_head *bh;
int sync = IS_DIRSYNC(inode);
+ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
+ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
+ num_extra = 0;
+
+ if (old_es && num_extra > 0)
+ src_start = old_es->num_entries - num_extra;
+
ep = exfat_get_dentry(sb, p_dir, entry, &bh);
if (!ep)
return -EIO;
- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
+ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
exfat_update_bh(bh, sync);
brelse(bh);
@@ -579,6 +601,19 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
exfat_update_bh(bh, sync);
brelse(bh);
+ if (old_es && num_extra > 0) {
+ for (i = 0; i < num_extra; i++) {
+ ep = exfat_get_dentry(sb, p_dir,
+ entry + num_entries + i, &bh);
+ if (!ep)
+ return -EIO;
+
+ *ep = *exfat_get_dentry_cached(old_es, src_start + i);
+ exfat_update_bh(bh, sync);
+ brelse(bh);
+ }
+ }
+
for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) {
ep = exfat_get_dentry(sb, p_dir, entry + i, &bh);
if (!ep)
@@ -595,7 +630,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
}
int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
- int entry, int order, int num_entries)
+ int entry, int order, int num_entries, bool free_benign)
{
struct super_block *sb = inode->i_sb;
int i;
@@ -607,7 +642,7 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
if (!ep)
return -EIO;
- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
+ if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
exfat_free_benign_secondary_clusters(inode, ep);
exfat_set_entry_type(ep, TYPE_DELETED);
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index e154284be63b6f..96b1faa25aaa9a 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -480,9 +480,10 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir,
int entry, unsigned int type, unsigned int start_clu,
unsigned long long size);
int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
- int entry, int num_entries, struct exfat_uni_name *p_uniname);
+ int entry, int num_entries, struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra);
int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
- int entry, int order, int num_entries);
+ int entry, int order, int num_entries, bool free_benign);
int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir,
int entry);
void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es);
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index f0856f7d9d5910..83a146d3c684ce 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -531,7 +531,8 @@ static int exfat_add_entry(struct inode *inode, const char *path,
if (ret)
goto out;
- ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname);
+ ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname,
+ NULL, 0);
if (ret)
goto out;
@@ -814,7 +815,8 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
/* update the directory entry */
- if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries)) {
+ if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries,
+ true)) {
err = -EIO;
goto unlock;
}
@@ -981,7 +983,8 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
brelse(bh);
exfat_set_volume_dirty(sb);
- err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries);
+ err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries,
+ true);
if (err) {
exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err);
goto unlock;
@@ -1007,41 +1010,67 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err;
}
+/*
+ * Count benign secondary entries beyond the filename entries.
+ * Returns the count, or -EIO if the entry set is inconsistent.
+ */
+static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
+{
+ struct exfat_dentry *stream;
+ unsigned int name_entries;
+ int extra;
+
+ stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
+ name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
+ extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
+
+ return extra >= 0 ? extra : -EIO;
+}
+
static int exfat_rename_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, num_old_entries, num_new_entries;
struct exfat_dentry *epold, *epnew;
struct super_block *sb = parent_inode->i_sb;
+ struct exfat_entry_set_cache old_es;
struct buffer_head *new_bh, *old_bh;
int sync = IS_DIRSYNC(parent_inode);
-
- epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh);
- if (!epold)
- return -EIO;
-
- num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
- epold);
- if (num_old_entries < 0)
- return -EIO;
- num_old_entries++;
+ unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
return num_new_entries;
- if (num_old_entries < num_new_entries) {
+ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
+ if (ret)
+ return -EIO;
+
+ num_old_entries = old_es.num_entries;
+
+ ret = exfat_count_extra_entries(&old_es);
+ if (ret < 0)
+ goto put_old_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
+ if (num_old_entries < num_total_entries) {
int newentry;
struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir,
- num_new_entries);
- if (newentry < 0)
- return newentry; /* -EIO or -ENOSPC */
+ num_total_entries);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_old_es;
+ }
+ epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
epnew = exfat_get_dentry(sb, &dir, newentry, &new_bh);
- if (!epnew)
- return -EIO;
+ if (!epnew) {
+ ret = -EIO;
+ goto put_old_es;
+ }
*epnew = *epold;
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
@@ -1049,33 +1078,43 @@ static int exfat_rename_file(struct inode *parent_inode,
ei->attr |= ATTR_ARCHIVE;
}
exfat_update_bh(new_bh, sync);
- brelse(old_bh);
brelse(new_bh);
- epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh);
- if (!epold)
- return -EIO;
+ epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM);
epnew = exfat_get_dentry(sb, &dir, newentry + 1, &new_bh);
if (!epnew) {
- brelse(old_bh);
- return -EIO;
+ /* Best-effort delete to avoid duplicate entries */
+ exfat_remove_entries(parent_inode, &dir, newentry, 0,
+ num_total_entries, false);
+ ret = -EIO;
+ goto put_old_es;
}
*epnew = *epold;
exfat_update_bh(new_bh, sync);
- brelse(old_bh);
brelse(new_bh);
ret = exfat_init_ext_entry(parent_inode, &dir, newentry,
- num_new_entries, p_uniname);
- if (ret)
- return ret;
+ num_new_entries, p_uniname, &old_es,
+ num_extra_entries);
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ exfat_remove_entries(parent_inode, &dir, newentry, 0,
+ num_total_entries, false);
+ goto put_old_es;
+ }
exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
- num_old_entries);
+ num_old_entries, false);
ei->dir = dir;
ei->entry = newentry;
} else {
+ epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh);
+ if (!epold) {
+ ret = -EIO;
+ goto put_old_es;
+ }
+
if (exfat_get_entry_type(epold) == TYPE_FILE) {
epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
ei->attr |= ATTR_ARCHIVE;
@@ -1083,15 +1122,21 @@ static int exfat_rename_file(struct inode *parent_inode,
exfat_update_bh(old_bh, sync);
brelse(old_bh);
- exfat_remove_entries(parent_inode, &ei->dir, ei->entry,
- ES_IDX_FIRST_FILENAME + 1, num_old_entries);
-
ret = exfat_init_ext_entry(parent_inode, &ei->dir, ei->entry,
- num_new_entries, p_uniname);
+ num_new_entries, p_uniname, &old_es,
+ num_extra_entries);
if (ret)
- return ret;
+ goto put_old_es;
+
+ /* Mark excess old entries as deleted (in-place shrink) */
+ exfat_remove_entries(parent_inode, &ei->dir, ei->entry,
+ num_total_entries, num_old_entries, false);
}
- return 0;
+ ret = 0;
+
+put_old_es:
+ exfat_put_dentry_set(&old_es, false);
+ return ret;
}
static int exfat_move_file(struct inode *parent_inode,
@@ -1100,31 +1145,40 @@ static int exfat_move_file(struct inode *parent_inode,
int ret, newentry, num_new_entries, num_old_entries;
struct exfat_dentry *epmov, *epnew;
struct super_block *sb = parent_inode->i_sb;
- struct buffer_head *mov_bh, *new_bh;
+ struct exfat_entry_set_cache mov_es;
+ struct buffer_head *new_bh;
struct exfat_chain newdir;
-
- epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh);
- if (!epmov)
- return -EIO;
-
- num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
- epmov);
- if (num_old_entries < 0)
- return -EIO;
- num_old_entries++;
+ unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
return num_new_entries;
+ ret = exfat_get_dentry_set_by_ei(&mov_es, sb, ei);
+ if (ret)
+ return -EIO;
+
+ num_old_entries = mov_es.num_entries;
+
+ ret = exfat_count_extra_entries(&mov_es);
+ if (ret < 0)
+ goto put_mov_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
newentry = exfat_find_empty_entry(parent_inode, &newdir,
- num_new_entries);
- if (newentry < 0)
- return newentry; /* -EIO or -ENOSPC */
+ num_total_entries);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_mov_es;
+ }
+ epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE);
epnew = exfat_get_dentry(sb, &newdir, newentry, &new_bh);
- if (!epnew)
- return -EIO;
+ if (!epnew) {
+ ret = -EIO;
+ goto put_mov_es;
+ }
*epnew = *epmov;
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
@@ -1132,34 +1186,41 @@ static int exfat_move_file(struct inode *parent_inode,
ei->attr |= ATTR_ARCHIVE;
}
exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
- brelse(mov_bh);
brelse(new_bh);
- epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh);
- if (!epmov)
- return -EIO;
+ epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM);
epnew = exfat_get_dentry(sb, &newdir, newentry + 1, &new_bh);
if (!epnew) {
- brelse(mov_bh);
- return -EIO;
+ /* Best-effort delete to avoid duplicate entries */
+ exfat_remove_entries(parent_inode, &newdir, newentry, 0,
+ num_total_entries, false);
+ ret = -EIO;
+ goto put_mov_es;
}
*epnew = *epmov;
exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
- brelse(mov_bh);
brelse(new_bh);
ret = exfat_init_ext_entry(parent_inode, &newdir, newentry,
- num_new_entries, p_uniname);
- if (ret)
- return ret;
+ num_new_entries, p_uniname, &mov_es, num_extra_entries);
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ exfat_remove_entries(parent_inode, &newdir, newentry, 0,
+ num_total_entries, false);
+ goto put_mov_es;
+ }
exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
- num_old_entries);
+ num_old_entries, false);
ei->dir = newdir;
ei->entry = newentry;
- return 0;
+ ret = 0;
+
+put_mov_es:
+ exfat_put_dentry_set(&mov_es, false);
+ return ret;
}
/* rename or move a old file into a new file */
@@ -1243,7 +1304,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
brelse(new_bh);
if (exfat_remove_entries(new_inode, &new_ei->dir, new_ei->entry,
- 0, num_entries + 1)) {
+ 0, num_entries + 1, true)) {
ret = -EIO;
goto del_out;
}
--
2.53.0
prev parent reply other threads:[~2026-07-21 11:47 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-15 14:41 FAILED: patch "[PATCH] exfat: preserve benign secondary entries during rename and" failed to apply to 6.6-stable tree gregkh
2026-07-21 11:47 ` [PATCH 6.6.y 1/6] exfat: move free cluster out of exfat_init_ext_entry() Sasha Levin
2026-07-21 11:47 ` [PATCH 6.6.y 2/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
2026-07-21 11:47 ` [PATCH 6.6.y 3/6] exfat: rename argument name for exfat_move_file and exfat_rename_file Sasha Levin
2026-07-21 11:47 ` [PATCH 6.6.y 4/6] exfat: add exfat_get_dentry_set_by_ei() helper Sasha Levin
2026-07-21 11:47 ` [PATCH 6.6.y 5/6] exfat: move exfat_chain_set() out of __exfat_resolve_path() Sasha Levin
2026-07-21 11:47 ` Sasha Levin [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260721114743.3689685-6-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=Yuezhang.Mo@sony.com \
--cc=linkinjeon@kernel.org \
--cc=rochan.avlur@gmail.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.