Skip to content

Commit a9e1ddc

Browse files
konisakpm00
authored andcommitted
nilfs2: fix kernel bug on rename operation of broken directory
Syzbot reported that in rename directory operation on broken directory on nilfs2, __block_write_begin_int() called to prepare block write may fail BUG_ON check for access exceeding the folio/page size. This is because nilfs_dotdot(), which gets parent directory reference entry ("..") of the directory to be moved or renamed, does not check consistency enough, and may return location exceeding folio/page size for broken directories. Fix this issue by checking required directory entries ("." and "..") in the first chunk of the directory in nilfs_dotdot(). Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Ryusuke Konishi <[email protected]> Reported-by: [email protected] Closes: https://syzkaller.appspot.com/bug?extid=d3abed1ad3d367fa2627 Fixes: 2ba466d ("nilfs2: directory entry operations") Tested-by: Ryusuke Konishi <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent bd22553 commit a9e1ddc

File tree

1 file changed

+30
-2
lines changed

1 file changed

+30
-2
lines changed

fs/nilfs2/dir.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,39 @@ struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,
383383

384384
struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct folio **foliop)
385385
{
386-
struct nilfs_dir_entry *de = nilfs_get_folio(dir, 0, foliop);
386+
struct folio *folio;
387+
struct nilfs_dir_entry *de, *next_de;
388+
size_t limit;
389+
char *msg;
387390

391+
de = nilfs_get_folio(dir, 0, &folio);
388392
if (IS_ERR(de))
389393
return NULL;
390-
return nilfs_next_entry(de);
394+
395+
limit = nilfs_last_byte(dir, 0); /* is a multiple of chunk size */
396+
if (unlikely(!limit || le64_to_cpu(de->inode) != dir->i_ino ||
397+
!nilfs_match(1, ".", de))) {
398+
msg = "missing '.'";
399+
goto fail;
400+
}
401+
402+
next_de = nilfs_next_entry(de);
403+
/*
404+
* If "next_de" has not reached the end of the chunk, there is
405+
* at least one more record. Check whether it matches "..".
406+
*/
407+
if (unlikely((char *)next_de == (char *)de + nilfs_chunk_size(dir) ||
408+
!nilfs_match(2, "..", next_de))) {
409+
msg = "missing '..'";
410+
goto fail;
411+
}
412+
*foliop = folio;
413+
return next_de;
414+
415+
fail:
416+
nilfs_error(dir->i_sb, "directory #%lu %s", dir->i_ino, msg);
417+
folio_release_kmap(folio, de);
418+
return NULL;
391419
}
392420

393421
ino_t nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr)

0 commit comments

Comments
 (0)