nilfs2: propagate directory read errors from nilfs_find_entry()

Syzbot reported that a task hang occurs in vcs_open() during a fuzzing
test for nilfs2.

The root cause of this problem is that in nilfs_find_entry(), which
searches for directory entries, ignores errors when loading a directory
page/folio via nilfs_get_folio() fails.

If the filesystem images is corrupted, and the i_size of the directory
inode is large, and the directory page/folio is successfully read but
fails the sanity check, for example when it is zero-filled,
nilfs_check_folio() may continue to spit out error messages in bursts.

Fix this issue by propagating the error to the callers when loading a
page/folio fails in nilfs_find_entry().

The current interface of nilfs_find_entry() and its callers is outdated
and cannot propagate error codes such as -EIO and -ENOMEM returned via
nilfs_find_entry(), so fix it together.

Link: https://lkml.kernel.org/r/20241004033640.6841-1-konishi.ryusuke@gmail.com
Fixes: 2ba466d74e ("nilfs2: directory entry operations")
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Reported-by: Lizhi Xu <lizhi.xu@windriver.com>
Closes: https://lkml.kernel.org/r/20240927013806.3577931-1-lizhi.xu@windriver.com
Reported-by: syzbot+8a192e8d090fa9a31135@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=8a192e8d090fa9a31135
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Ryusuke Konishi 2024-10-04 12:35:31 +09:00 committed by Andrew Morton
parent 74874c5793
commit 08cfa12adf
3 changed files with 52 additions and 37 deletions

View File

@ -289,7 +289,7 @@ static int nilfs_readdir(struct file *file, struct dir_context *ctx)
* The folio is mapped and unlocked. When the caller is finished with * The folio is mapped and unlocked. When the caller is finished with
* the entry, it should call folio_release_kmap(). * the entry, it should call folio_release_kmap().
* *
* On failure, returns NULL and the caller should ignore foliop. * On failure, returns an error pointer and the caller should ignore foliop.
*/ */
struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir, struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,
const struct qstr *qstr, struct folio **foliop) const struct qstr *qstr, struct folio **foliop)
@ -312,22 +312,24 @@ struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,
do { do {
char *kaddr = nilfs_get_folio(dir, n, foliop); char *kaddr = nilfs_get_folio(dir, n, foliop);
if (!IS_ERR(kaddr)) { if (IS_ERR(kaddr))
de = (struct nilfs_dir_entry *)kaddr; return ERR_CAST(kaddr);
kaddr += nilfs_last_byte(dir, n) - reclen;
while ((char *) de <= kaddr) { de = (struct nilfs_dir_entry *)kaddr;
if (de->rec_len == 0) { kaddr += nilfs_last_byte(dir, n) - reclen;
nilfs_error(dir->i_sb, while ((char *)de <= kaddr) {
"zero-length directory entry"); if (de->rec_len == 0) {
folio_release_kmap(*foliop, kaddr); nilfs_error(dir->i_sb,
goto out; "zero-length directory entry");
} folio_release_kmap(*foliop, kaddr);
if (nilfs_match(namelen, name, de)) goto out;
goto found;
de = nilfs_next_entry(de);
} }
folio_release_kmap(*foliop, kaddr); if (nilfs_match(namelen, name, de))
goto found;
de = nilfs_next_entry(de);
} }
folio_release_kmap(*foliop, kaddr);
if (++n >= npages) if (++n >= npages)
n = 0; n = 0;
/* next folio is past the blocks we've got */ /* next folio is past the blocks we've got */
@ -340,7 +342,7 @@ struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,
} }
} while (n != start); } while (n != start);
out: out:
return NULL; return ERR_PTR(-ENOENT);
found: found:
ei->i_dir_start_lookup = n; ei->i_dir_start_lookup = n;
@ -384,18 +386,18 @@ fail:
return NULL; return NULL;
} }
ino_t nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr) int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino)
{ {
ino_t res = 0;
struct nilfs_dir_entry *de; struct nilfs_dir_entry *de;
struct folio *folio; struct folio *folio;
de = nilfs_find_entry(dir, qstr, &folio); de = nilfs_find_entry(dir, qstr, &folio);
if (de) { if (IS_ERR(de))
res = le64_to_cpu(de->inode); return PTR_ERR(de);
folio_release_kmap(folio, de);
} *ino = le64_to_cpu(de->inode);
return res; folio_release_kmap(folio, de);
return 0;
} }
void nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de, void nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,

View File

@ -55,12 +55,20 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
{ {
struct inode *inode; struct inode *inode;
ino_t ino; ino_t ino;
int res;
if (dentry->d_name.len > NILFS_NAME_LEN) if (dentry->d_name.len > NILFS_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG); return ERR_PTR(-ENAMETOOLONG);
ino = nilfs_inode_by_name(dir, &dentry->d_name); res = nilfs_inode_by_name(dir, &dentry->d_name, &ino);
inode = ino ? nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino) : NULL; if (res) {
if (res != -ENOENT)
return ERR_PTR(res);
inode = NULL;
} else {
inode = nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino);
}
return d_splice_alias(inode, dentry); return d_splice_alias(inode, dentry);
} }
@ -263,10 +271,11 @@ static int nilfs_do_unlink(struct inode *dir, struct dentry *dentry)
struct folio *folio; struct folio *folio;
int err; int err;
err = -ENOENT;
de = nilfs_find_entry(dir, &dentry->d_name, &folio); de = nilfs_find_entry(dir, &dentry->d_name, &folio);
if (!de) if (IS_ERR(de)) {
err = PTR_ERR(de);
goto out; goto out;
}
inode = d_inode(dentry); inode = d_inode(dentry);
err = -EIO; err = -EIO;
@ -362,10 +371,11 @@ static int nilfs_rename(struct mnt_idmap *idmap,
if (unlikely(err)) if (unlikely(err))
return err; return err;
err = -ENOENT;
old_de = nilfs_find_entry(old_dir, &old_dentry->d_name, &old_folio); old_de = nilfs_find_entry(old_dir, &old_dentry->d_name, &old_folio);
if (!old_de) if (IS_ERR(old_de)) {
err = PTR_ERR(old_de);
goto out; goto out;
}
if (S_ISDIR(old_inode->i_mode)) { if (S_ISDIR(old_inode->i_mode)) {
err = -EIO; err = -EIO;
@ -382,10 +392,12 @@ static int nilfs_rename(struct mnt_idmap *idmap,
if (dir_de && !nilfs_empty_dir(new_inode)) if (dir_de && !nilfs_empty_dir(new_inode))
goto out_dir; goto out_dir;
err = -ENOENT; new_de = nilfs_find_entry(new_dir, &new_dentry->d_name,
new_de = nilfs_find_entry(new_dir, &new_dentry->d_name, &new_folio); &new_folio);
if (!new_de) if (IS_ERR(new_de)) {
err = PTR_ERR(new_de);
goto out_dir; goto out_dir;
}
nilfs_set_link(new_dir, new_de, new_folio, old_inode); nilfs_set_link(new_dir, new_de, new_folio, old_inode);
folio_release_kmap(new_folio, new_de); folio_release_kmap(new_folio, new_de);
nilfs_mark_inode_dirty(new_dir); nilfs_mark_inode_dirty(new_dir);
@ -440,12 +452,13 @@ out:
*/ */
static struct dentry *nilfs_get_parent(struct dentry *child) static struct dentry *nilfs_get_parent(struct dentry *child)
{ {
unsigned long ino; ino_t ino;
int res;
struct nilfs_root *root; struct nilfs_root *root;
ino = nilfs_inode_by_name(d_inode(child), &dotdot_name); res = nilfs_inode_by_name(d_inode(child), &dotdot_name, &ino);
if (!ino) if (res)
return ERR_PTR(-ENOENT); return ERR_PTR(res);
root = NILFS_I(d_inode(child))->i_root; root = NILFS_I(d_inode(child))->i_root;

View File

@ -254,7 +254,7 @@ static inline __u32 nilfs_mask_flags(umode_t mode, __u32 flags)
/* dir.c */ /* dir.c */
int nilfs_add_link(struct dentry *, struct inode *); int nilfs_add_link(struct dentry *, struct inode *);
ino_t nilfs_inode_by_name(struct inode *, const struct qstr *); int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino);
int nilfs_make_empty(struct inode *, struct inode *); int nilfs_make_empty(struct inode *, struct inode *);
struct nilfs_dir_entry *nilfs_find_entry(struct inode *, const struct qstr *, struct nilfs_dir_entry *nilfs_find_entry(struct inode *, const struct qstr *,
struct folio **); struct folio **);