mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Krystian Kaniewski <krystianmkaniewski@gmail.com>
To: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	syzbot+b0aebd03565f5774f7f8@syzkaller.appspotmail.com
Subject: [PATCH v5] fat: make rename recovery safe after metadata write errors
Date: Mon,  2 Nov 2026 15:30:11 +0100	[thread overview]
Message-ID: <20261102143013.492650-1-krystianmkaniewski@gmail.com> (raw)
In-Reply-To: <2ba329a1-ad8c-4b6a-8dd6-1f93c85224c0@gmail.com>

A failed synchronous FAT metadata write clears BH_Uptodate. Rename
recovery can then modify and dirty the same retained buffer, which
triggers the !buffer_uptodate warning in mark_buffer_dirty().

Check the retained buffers in the rename error paths and skip only the
recovery step whose buffer is no longer uptodate. The other steps run as
before, including the rollback after a failed source-entry removal,
where only the source-entry buffer failed. VFAT keeps no slot
information for a replaced entry. Resolve its saved position with a
cache-only lookup before dirtying the inode, because FAT inode
writeback would otherwise reread and retry a failed block.

For exchange, do not rewrite the dotdot buffer whose write failed,
restore an earlier dotdot update only while its buffer is uptodate, and
restore both inode positions as before. A skipped step is reported as
filesystem corruption, and the original error is returned.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: syzbot+b0aebd03565f5774f7f8@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b0aebd03565f5774f7f8
Signed-off-by: Krystian Kaniewski <krystianmkaniewski@gmail.com>
---
Changes in v5:
- Check buffer_uptodate() directly in the rename error paths and drop the
  helper and the buffer lock of v4.
- Skip only the recovery step of a buffer that is no longer uptodate and
  keep the rest of the existing recovery, also after a failed dotdot write
  and in exchange.
- Keep the existing rollback after a failed source-entry removal. The reply
  to v4 proposed stopping there, but fat_remove_entries() fails only when
  the old_sinfo buffer write fails, so the source entry is still in place.
  Stopping left an invalid ".." entry and shared clusters in my tests,
  while the existing rollback leaves the filesystem clean.
- Look up the cached VFAT target entry block from its inode position,
  because that path has no slot information.
- Clarify the recovery-state comments without changing behavior.

v4: https://lore.kernel.org/all/20261019145146.539233-1-krystianmkaniewski@gmail.com/

 fs/fat/namei_msdos.c | 18 ++++++++++++----
 fs/fat/namei_vfat.c  | 50 +++++++++++++++++++++++++++++++-------------
 2 files changed, 49 insertions(+), 19 deletions(-)

diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index d46d1a3851f25f..51d49a3ca93c14 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -562,14 +562,20 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
 	return err;
 
 error_dotdot:
-	/* data cluster is shared, serious corruption */
+	/*
+	 * Complete the remaining rollback. Report corruption only if a
+	 * recovery step cannot be completed.
+	 */
 	corrupt = 1;
 
-	if (update_dotdot) {
+	/* A failed sync write clears BH_Uptodate, leave that buffer alone. */
+	if (update_dotdot && buffer_uptodate(dotdot_bh)) {
 		fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
 		mmb_mark_buffer_dirty(dotdot_bh,
 				      &MSDOS_I(old_inode)->i_metadata_bhs);
 		corrupt |= sync_dirty_buffer(dotdot_bh);
+	} else if (update_dotdot) {
+		corrupt = err;
 	}
 error_inode:
 	fat_detach(old_inode);
@@ -577,11 +583,13 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
 	MSDOS_I(old_inode)->i_attrs = old_attrs;
 	if (new_inode) {
 		fat_attach(new_inode, new_i_pos);
-		if (corrupt) {
+		if (corrupt && buffer_uptodate(sinfo.bh)) {
 			mark_inode_dirty(new_inode);
 			corrupt |= sync_inode_metadata(new_inode, 1);
+		} else if (corrupt) {
+			corrupt = err;
 		}
-	} else {
+	} else if (buffer_uptodate(sinfo.bh)) {
 		/*
 		 * If new entry was not sharing the data cluster, it
 		 * shouldn't be serious corruption.
@@ -590,6 +598,8 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
 		if (corrupt)
 			corrupt |= err2;
 		sinfo.bh = NULL;
+	} else {
+		corrupt = err;
 	}
 	if (corrupt < 0) {
 		fat_fs_error(new_dir->i_sb,
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index da3e89c0b16ac8..31f9882f0a9ffe 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -1010,12 +1010,18 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
 	return err;
 
 error_dotdot:
-	/* data cluster is shared, serious corruption */
+	/*
+	 * Complete the remaining rollback. Report corruption only if a
+	 * recovery step cannot be completed.
+	 */
 	corrupt = 1;
 
-	if (dotdot_de) {
+	/* A failed sync write clears BH_Uptodate, leave that buffer alone. */
+	if (dotdot_de && buffer_uptodate(dotdot_bh)) {
 		corrupt |= vfat_update_dotdot_de(old_dir, old_inode, dotdot_bh,
 						 dotdot_de);
+	} else if (dotdot_de) {
+		corrupt = err;
 	}
 error_inode:
 	fat_detach(old_inode);
@@ -1023,10 +1029,27 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (new_inode) {
 		fat_attach(new_inode, new_i_pos);
 		if (corrupt) {
-			mark_inode_dirty(new_inode);
-			corrupt |= sync_inode_metadata(new_inode, 1);
+			struct buffer_head *target_bh;
+			sector_t blocknr;
+			int offset;
+
+			/*
+			 * A replaced entry has no slot info. Use its saved
+			 * position for a cache-only lookup so recovery does
+			 * not reread a failed block.
+			 */
+			fat_get_blknr_offset(MSDOS_SB(sb), new_i_pos,
+					     &blocknr, &offset);
+			target_bh = sb_find_get_block_nonatomic(sb, blocknr);
+			if (target_bh && buffer_uptodate(target_bh)) {
+				mark_inode_dirty(new_inode);
+				corrupt |= sync_inode_metadata(new_inode, 1);
+			} else {
+				corrupt = err;
+			}
+			brelse(target_bh);
 		}
-	} else {
+	} else if (buffer_uptodate(sinfo.bh)) {
 		/*
 		 * If new entry was not sharing the data cluster, it
 		 * shouldn't be serious corruption.
@@ -1035,6 +1058,8 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
 		if (corrupt)
 			corrupt |= err2;
 		sinfo.bh = NULL;
+	} else {
+		corrupt = err;
 	}
 	if (corrupt < 0) {
 		fat_fs_error(new_dir->i_sb,
@@ -1106,13 +1131,13 @@ static int vfat_rename_exchange(struct inode *old_dir, struct dentry *old_dentry
 		err = vfat_update_dotdot_de(new_dir, old_inode, old_dotdot_bh,
 					    old_dotdot_de);
 		if (err)
-			goto error_old_dotdot;
+			goto error_dotdot;
 	}
 	if (new_dotdot_de) {
 		err = vfat_update_dotdot_de(old_dir, new_inode, new_dotdot_bh,
 					    new_dotdot_de);
 		if (err)
-			goto error_new_dotdot;
+			goto error_dotdot;
 	}
 
 	/* if cross directory and only one is a directory, adjust nlink */
@@ -1135,14 +1160,9 @@ static int vfat_rename_exchange(struct inode *old_dir, struct dentry *old_dentry
 
 	return err;
 
-error_new_dotdot:
-	if (new_dotdot_de) {
-		corrupt |= vfat_update_dotdot_de(new_dir, new_inode,
-						 new_dotdot_bh, new_dotdot_de);
-	}
-
-error_old_dotdot:
-	if (old_dotdot_de) {
+error_dotdot:
+	corrupt = err;
+	if (old_dotdot_de && buffer_uptodate(old_dotdot_bh)) {
 		corrupt |= vfat_update_dotdot_de(old_dir, old_inode,
 						 old_dotdot_bh, old_dotdot_de);
 	}

base-commit: 93f51579e7df248780214094418f205253383cc5
-- 
2.53.0


      parent reply	other threads:[~2026-11-02 14:30 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-14 16:38 [syzbot] [exfat?] WARNING in vfat_update_dotdot_de syzbot
2026-09-29  9:00 ` [PATCH] fat: validate dotdot buffers in VFAT and MSDOS rename and rollback Krystian Kaniewski
2026-09-30  8:02   ` OGAWA Hirofumi
2026-09-30 12:17     ` Krystian Kaniewski
2026-10-02 13:43     ` [PATCH v2] fat: skip rename rollback on buffers with failed I/O Krystian Kaniewski
2026-10-11 18:36       ` OGAWA Hirofumi
2026-10-14  8:21         ` Krystian Kaniewski
2026-10-14  8:23         ` [PATCH v3] fat: make rename recovery safe after metadata write errors Krystian Kaniewski
2026-10-15 18:54           ` OGAWA Hirofumi
2026-10-16 13:30             ` Krystian Kaniewski
2026-10-16 13:50               ` OGAWA Hirofumi
2026-10-16 14:01                 ` Krystian Kaniewski
2026-10-16 15:23                   ` OGAWA Hirofumi
2026-10-19  8:21                     ` Krystian Kaniewski
2026-10-19  9:55                       ` OGAWA Hirofumi
2026-10-19 14:51                         ` [PATCH v4] " Krystian Kaniewski
2026-10-19 16:15                           ` OGAWA Hirofumi
     [not found]                             ` <2ba329a1-ad8c-4b6a-8dd6-1f93c85224c0@gmail.com>
2026-11-02 14:30                               ` Krystian Kaniewski [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=20261102143013.492650-1-krystianmkaniewski@gmail.com \
    --to=krystianmkaniewski@gmail.com \
    --cc=hirofumi@mail.parknet.co.jp \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=syzbot+b0aebd03565f5774f7f8@syzkaller.appspotmail.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®