Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb857ae1 authored by Ryusuke Konishi's avatar Ryusuke Konishi Committed by Greg Kroah-Hartman
Browse files

nilfs2: propagate directory read errors from nilfs_find_entry()

commit 08cfa12adf888db98879dbd735bc741360a34168 upstream.

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: 2ba466d7 ("nilfs2: directory entry operations")
Signed-off-by: default avatarRyusuke Konishi <konishi.ryusuke@gmail.com>
Reported-by: default avatarLizhi Xu <lizhi.xu@windriver.com>
Closes: https://lkml.kernel.org/r/20240927013806.3577931-1-lizhi.xu@windriver.com


Reported-by: default avatar <syzbot+8a192e8d090fa9a31135@syzkaller.appspotmail.com>
Closes: https://syzkaller.appspot.com/bug?extid=8a192e8d090fa9a31135


Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent e7556234
Loading
Loading
Loading
Loading
+27 −23
Original line number Diff line number Diff line
@@ -331,6 +331,8 @@ static int nilfs_readdir(struct file *file, struct dir_context *ctx)
 * returns the page in which the entry was found, and the entry itself
 * (as a parameter - res_dir). Page is returned mapped and unlocked.
 * Entry is guaranteed to be valid.
 *
 * On failure, returns an error pointer and the caller should ignore res_page.
 */
struct nilfs_dir_entry *
nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
@@ -358,7 +360,9 @@ nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
	do {
		char *kaddr = nilfs_get_page(dir, n, &page);

		if (!IS_ERR(kaddr)) {
		if (IS_ERR(kaddr))
			return ERR_CAST(kaddr);

		de = (struct nilfs_dir_entry *)kaddr;
		kaddr += nilfs_last_byte(dir, n) - reclen;
		while ((char *)de <= kaddr) {
@@ -373,7 +377,7 @@ nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
			de = nilfs_next_entry(de);
		}
		nilfs_put_page(page);
		}

		if (++n >= npages)
			n = 0;
		/* next page is past the blocks we've got */
@@ -386,7 +390,7 @@ nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
		}
	} while (n != start);
out:
	return NULL;
	return ERR_PTR(-ENOENT);

found:
	*res_page = page;
@@ -431,19 +435,19 @@ struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct page **p)
	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 page *page;

	de = nilfs_find_entry(dir, qstr, &page);
	if (de) {
		res = le64_to_cpu(de->inode);
	if (IS_ERR(de))
		return PTR_ERR(de);

	*ino = le64_to_cpu(de->inode);
	kunmap(page);
	put_page(page);
	}
	return res;
	return 0;
}

/* Releases the page */
+26 −13
Original line number Diff line number Diff line
@@ -55,12 +55,20 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
{
	struct inode *inode;
	ino_t ino;
	int res;

	if (dentry->d_name.len > NILFS_NAME_LEN)
		return ERR_PTR(-ENAMETOOLONG);

	ino = nilfs_inode_by_name(dir, &dentry->d_name);
	inode = ino ? nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino) : NULL;
	res = nilfs_inode_by_name(dir, &dentry->d_name, &ino);
	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);
}

@@ -261,10 +269,11 @@ static int nilfs_do_unlink(struct inode *dir, struct dentry *dentry)
	struct page *page;
	int err;

	err = -ENOENT;
	de = nilfs_find_entry(dir, &dentry->d_name, &page);
	if (!de)
	if (IS_ERR(de)) {
		err = PTR_ERR(de);
		goto out;
	}

	inode = d_inode(dentry);
	err = -EIO;
@@ -358,10 +367,11 @@ static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry,
	if (unlikely(err))
		return err;

	err = -ENOENT;
	old_de = nilfs_find_entry(old_dir, &old_dentry->d_name, &old_page);
	if (!old_de)
	if (IS_ERR(old_de)) {
		err = PTR_ERR(old_de);
		goto out;
	}

	if (S_ISDIR(old_inode->i_mode)) {
		err = -EIO;
@@ -378,10 +388,12 @@ static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry,
		if (dir_de && !nilfs_empty_dir(new_inode))
			goto out_dir;

		err = -ENOENT;
		new_de = nilfs_find_entry(new_dir, &new_dentry->d_name, &new_page);
		if (!new_de)
		new_de = nilfs_find_entry(new_dir, &new_dentry->d_name,
					  &new_page);
		if (IS_ERR(new_de)) {
			err = PTR_ERR(new_de);
			goto out_dir;
		}
		nilfs_set_link(new_dir, new_de, new_page, old_inode);
		nilfs_mark_inode_dirty(new_dir);
		new_inode->i_ctime = current_time(new_inode);
@@ -435,14 +447,15 @@ static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 */
static struct dentry *nilfs_get_parent(struct dentry *child)
{
	unsigned long ino;
	ino_t ino;
	int res;
	struct inode *inode;
	struct qstr dotdot = QSTR_INIT("..", 2);
	struct nilfs_root *root;

	ino = nilfs_inode_by_name(d_inode(child), &dotdot);
	if (!ino)
		return ERR_PTR(-ENOENT);
	res = nilfs_inode_by_name(d_inode(child), &dotdot, &ino);
	if (res)
		return ERR_PTR(res);

	root = NILFS_I(d_inode(child))->i_root;

+1 −1
Original line number Diff line number Diff line
@@ -233,7 +233,7 @@ static inline __u32 nilfs_mask_flags(umode_t mode, __u32 flags)

/* dir.c */
extern int nilfs_add_link(struct dentry *, struct inode *);
extern 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);
extern int nilfs_make_empty(struct inode *, struct inode *);
extern struct nilfs_dir_entry *
nilfs_find_entry(struct inode *, const struct qstr *, struct page **);