Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

Commit 9698088

Browse files
konisgregkh
authored andcommitted
nilfs2: propagate directory read errors from nilfs_find_entry()
commit 08cfa12 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: 2ba466d ("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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 87cb3f6 commit 9698088

File tree

3 files changed

+54
-37
lines changed

3 files changed

+54
-37
lines changed

fs/nilfs2/dir.c

+27-23
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ static int nilfs_readdir(struct file *file, struct dir_context *ctx)
331331
* returns the page in which the entry was found, and the entry itself
332332
* (as a parameter - res_dir). Page is returned mapped and unlocked.
333333
* Entry is guaranteed to be valid.
334+
*
335+
* On failure, returns an error pointer and the caller should ignore res_page.
334336
*/
335337
struct nilfs_dir_entry *
336338
nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
@@ -358,22 +360,24 @@ nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
358360
do {
359361
char *kaddr = nilfs_get_page(dir, n, &page);
360362

361-
if (!IS_ERR(kaddr)) {
362-
de = (struct nilfs_dir_entry *)kaddr;
363-
kaddr += nilfs_last_byte(dir, n) - reclen;
364-
while ((char *) de <= kaddr) {
365-
if (de->rec_len == 0) {
366-
nilfs_error(dir->i_sb,
367-
"zero-length directory entry");
368-
nilfs_put_page(page);
369-
goto out;
370-
}
371-
if (nilfs_match(namelen, name, de))
372-
goto found;
373-
de = nilfs_next_entry(de);
363+
if (IS_ERR(kaddr))
364+
return ERR_CAST(kaddr);
365+
366+
de = (struct nilfs_dir_entry *)kaddr;
367+
kaddr += nilfs_last_byte(dir, n) - reclen;
368+
while ((char *)de <= kaddr) {
369+
if (de->rec_len == 0) {
370+
nilfs_error(dir->i_sb,
371+
"zero-length directory entry");
372+
nilfs_put_page(page);
373+
goto out;
374374
}
375-
nilfs_put_page(page);
375+
if (nilfs_match(namelen, name, de))
376+
goto found;
377+
de = nilfs_next_entry(de);
376378
}
379+
nilfs_put_page(page);
380+
377381
if (++n >= npages)
378382
n = 0;
379383
/* next page is past the blocks we've got */
@@ -386,7 +390,7 @@ nilfs_find_entry(struct inode *dir, const struct qstr *qstr,
386390
}
387391
} while (n != start);
388392
out:
389-
return NULL;
393+
return ERR_PTR(-ENOENT);
390394

391395
found:
392396
*res_page = page;
@@ -431,19 +435,19 @@ struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct page **p)
431435
return NULL;
432436
}
433437

434-
ino_t nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr)
438+
int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino)
435439
{
436-
ino_t res = 0;
437440
struct nilfs_dir_entry *de;
438441
struct page *page;
439442

440443
de = nilfs_find_entry(dir, qstr, &page);
441-
if (de) {
442-
res = le64_to_cpu(de->inode);
443-
kunmap(page);
444-
put_page(page);
445-
}
446-
return res;
444+
if (IS_ERR(de))
445+
return PTR_ERR(de);
446+
447+
*ino = le64_to_cpu(de->inode);
448+
kunmap(page);
449+
put_page(page);
450+
return 0;
447451
}
448452

449453
/* Releases the page */

fs/nilfs2/namei.c

+26-13
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,20 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
5555
{
5656
struct inode *inode;
5757
ino_t ino;
58+
int res;
5859

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

62-
ino = nilfs_inode_by_name(dir, &dentry->d_name);
63-
inode = ino ? nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino) : NULL;
63+
res = nilfs_inode_by_name(dir, &dentry->d_name, &ino);
64+
if (res) {
65+
if (res != -ENOENT)
66+
return ERR_PTR(res);
67+
inode = NULL;
68+
} else {
69+
inode = nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino);
70+
}
71+
6472
return d_splice_alias(inode, dentry);
6573
}
6674

@@ -263,10 +271,11 @@ static int nilfs_do_unlink(struct inode *dir, struct dentry *dentry)
263271
struct page *page;
264272
int err;
265273

266-
err = -ENOENT;
267274
de = nilfs_find_entry(dir, &dentry->d_name, &page);
268-
if (!de)
275+
if (IS_ERR(de)) {
276+
err = PTR_ERR(de);
269277
goto out;
278+
}
270279

271280
inode = d_inode(dentry);
272281
err = -EIO;
@@ -361,10 +370,11 @@ static int nilfs_rename(struct mnt_idmap *idmap,
361370
if (unlikely(err))
362371
return err;
363372

364-
err = -ENOENT;
365373
old_de = nilfs_find_entry(old_dir, &old_dentry->d_name, &old_page);
366-
if (!old_de)
374+
if (IS_ERR(old_de)) {
375+
err = PTR_ERR(old_de);
367376
goto out;
377+
}
368378

369379
if (S_ISDIR(old_inode->i_mode)) {
370380
err = -EIO;
@@ -381,10 +391,12 @@ static int nilfs_rename(struct mnt_idmap *idmap,
381391
if (dir_de && !nilfs_empty_dir(new_inode))
382392
goto out_dir;
383393

384-
err = -ENOENT;
385-
new_de = nilfs_find_entry(new_dir, &new_dentry->d_name, &new_page);
386-
if (!new_de)
394+
new_de = nilfs_find_entry(new_dir, &new_dentry->d_name,
395+
&new_page);
396+
if (IS_ERR(new_de)) {
397+
err = PTR_ERR(new_de);
387398
goto out_dir;
399+
}
388400
nilfs_set_link(new_dir, new_de, new_page, old_inode);
389401
nilfs_mark_inode_dirty(new_dir);
390402
inode_set_ctime_current(new_inode);
@@ -438,13 +450,14 @@ static int nilfs_rename(struct mnt_idmap *idmap,
438450
*/
439451
static struct dentry *nilfs_get_parent(struct dentry *child)
440452
{
441-
unsigned long ino;
453+
ino_t ino;
454+
int res;
442455
struct inode *inode;
443456
struct nilfs_root *root;
444457

445-
ino = nilfs_inode_by_name(d_inode(child), &dotdot_name);
446-
if (!ino)
447-
return ERR_PTR(-ENOENT);
458+
res = nilfs_inode_by_name(d_inode(child), &dotdot_name, &ino);
459+
if (res)
460+
return ERR_PTR(res);
448461

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

fs/nilfs2/nilfs.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ static inline __u32 nilfs_mask_flags(umode_t mode, __u32 flags)
233233

234234
/* dir.c */
235235
extern int nilfs_add_link(struct dentry *, struct inode *);
236-
extern ino_t nilfs_inode_by_name(struct inode *, const struct qstr *);
236+
int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino);
237237
extern int nilfs_make_empty(struct inode *, struct inode *);
238238
extern struct nilfs_dir_entry *
239239
nilfs_find_entry(struct inode *, const struct qstr *, struct page **);

0 commit comments

Comments
 (0)