summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyusuke Konishi <konishi.ryusuke@gmail.com>2025-01-11 23:26:35 +0900
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2025-03-13 12:51:12 +0100
commit607dc724b162f4452dc768865e578c1a509a1c8c (patch)
treeb93eb75d17288987b9867d1f5a76763a7483e9dd
parente5606b783307b76b2d76feb8719a3ca7f036438b (diff)
downloadlinux-607dc724b162f4452dc768865e578c1a509a1c8c.tar.gz
linux-607dc724b162f4452dc768865e578c1a509a1c8c.tar.bz2
linux-607dc724b162f4452dc768865e578c1a509a1c8c.zip
nilfs2: handle errors that nilfs_prepare_chunk() may return
commit ee70999a988b8abc3490609142f50ebaa8344432 upstream. Patch series "nilfs2: fix issues with rename operations". This series fixes BUG_ON check failures reported by syzbot around rename operations, and a minor behavioral issue where the mtime of a child directory changes when it is renamed instead of moved. This patch (of 2): The directory manipulation routines nilfs_set_link() and nilfs_delete_entry() rewrite the directory entry in the folio/page previously read by nilfs_find_entry(), so error handling is omitted on the assumption that nilfs_prepare_chunk(), which prepares the buffer for rewriting, will always succeed for these. And if an error is returned, it triggers the legacy BUG_ON() checks in each routine. This assumption is wrong, as proven by syzbot: the buffer layer called by nilfs_prepare_chunk() may call nilfs_get_block() if necessary, which may fail due to metadata corruption or other reasons. This has been there all along, but improved sanity checks and error handling may have made it more reproducible in fuzzing tests. Fix this issue by adding missing error paths in nilfs_set_link(), nilfs_delete_entry(), and their caller nilfs_rename(). Link: https://lkml.kernel.org/r/20250111143518.7901-1-konishi.ryusuke@gmail.com Link: https://lkml.kernel.org/r/20250111143518.7901-2-konishi.ryusuke@gmail.com Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com> Reported-by: syzbot+32c3706ebf5d95046ea1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=32c3706ebf5d95046ea1 Reported-by: syzbot+1097e95f134f37d9395c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1097e95f134f37d9395c Fixes: 2ba466d74ed7 ("nilfs2: directory entry operations") Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/nilfs2/dir.c13
-rw-r--r--fs/nilfs2/namei.c29
-rw-r--r--fs/nilfs2/nilfs.h4
3 files changed, 27 insertions, 19 deletions
diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c
index 3a8ff6d4a1b0..3d7e692f3e7f 100644
--- a/fs/nilfs2/dir.c
+++ b/fs/nilfs2/dir.c
@@ -444,7 +444,7 @@ int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino)
return 0;
}
-void nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
+int nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
struct page *page, struct inode *inode)
{
unsigned int from = (char *)de - (char *)page_address(page);
@@ -454,11 +454,15 @@ void nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
lock_page(page);
err = nilfs_prepare_chunk(page, from, to);
- BUG_ON(err);
+ if (unlikely(err)) {
+ unlock_page(page);
+ return err;
+ }
de->inode = cpu_to_le64(inode->i_ino);
nilfs_set_de_type(de, inode);
nilfs_commit_chunk(page, mapping, from, to);
dir->i_mtime = dir->i_ctime = current_time(dir);
+ return 0;
}
/*
@@ -590,7 +594,10 @@ int nilfs_delete_entry(struct nilfs_dir_entry *dir, struct page *page)
from = (char *)pde - (char *)page_address(page);
lock_page(page);
err = nilfs_prepare_chunk(page, from, to);
- BUG_ON(err);
+ if (unlikely(err)) {
+ unlock_page(page);
+ goto out;
+ }
if (pde)
pde->rec_len = nilfs_rec_len_to_disk(to - from);
dir->inode = 0;
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 9dd4527d8f69..a81c24630e26 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -406,8 +406,10 @@ static int nilfs_rename(struct user_namespace *mnt_userns,
err = PTR_ERR(new_de);
goto out_dir;
}
- nilfs_set_link(new_dir, new_de, new_page, old_inode);
+ err = nilfs_set_link(new_dir, new_de, new_page, old_inode);
nilfs_put_page(new_page);
+ if (unlikely(err))
+ goto out_dir;
nilfs_mark_inode_dirty(new_dir);
new_inode->i_ctime = current_time(new_inode);
if (dir_de)
@@ -430,28 +432,27 @@ static int nilfs_rename(struct user_namespace *mnt_userns,
*/
old_inode->i_ctime = current_time(old_inode);
- nilfs_delete_entry(old_de, old_page);
-
- if (dir_de) {
- nilfs_set_link(old_inode, dir_de, dir_page, new_dir);
- nilfs_put_page(dir_page);
- drop_nlink(old_dir);
+ err = nilfs_delete_entry(old_de, old_page);
+ if (likely(!err)) {
+ if (dir_de) {
+ err = nilfs_set_link(old_inode, dir_de, dir_page,
+ new_dir);
+ drop_nlink(old_dir);
+ }
+ nilfs_mark_inode_dirty(old_dir);
}
- nilfs_put_page(old_page);
-
- nilfs_mark_inode_dirty(old_dir);
nilfs_mark_inode_dirty(old_inode);
- err = nilfs_transaction_commit(old_dir->i_sb);
- return err;
-
out_dir:
if (dir_de)
nilfs_put_page(dir_page);
out_old:
nilfs_put_page(old_page);
out:
- nilfs_transaction_abort(old_dir->i_sb);
+ if (likely(!err))
+ err = nilfs_transaction_commit(old_dir->i_sb);
+ else
+ nilfs_transaction_abort(old_dir->i_sb);
return err;
}
diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h
index 760f785c2b6d..14ae1e3df83f 100644
--- a/fs/nilfs2/nilfs.h
+++ b/fs/nilfs2/nilfs.h
@@ -240,8 +240,8 @@ nilfs_find_entry(struct inode *, const struct qstr *, struct page **);
extern int nilfs_delete_entry(struct nilfs_dir_entry *, struct page *);
extern int nilfs_empty_dir(struct inode *);
extern struct nilfs_dir_entry *nilfs_dotdot(struct inode *, struct page **);
-extern void nilfs_set_link(struct inode *, struct nilfs_dir_entry *,
- struct page *, struct inode *);
+int nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
+ struct page *page, struct inode *inode);
static inline void nilfs_put_page(struct page *page)
{