Skip to content

Commit

Permalink
nilfs2: fix kernel bug on rename operation of broken directory
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
konis authored and akpm00 committed Jul 4, 2024
1 parent bd22553 commit a9e1ddc
Showing 1 changed file with 30 additions and 2 deletions.
32 changes: 30 additions & 2 deletions fs/nilfs2/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,39 @@ struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,

struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct folio **foliop)
{
struct nilfs_dir_entry *de = nilfs_get_folio(dir, 0, foliop);
struct folio *folio;
struct nilfs_dir_entry *de, *next_de;
size_t limit;
char *msg;

de = nilfs_get_folio(dir, 0, &folio);
if (IS_ERR(de))
return NULL;
return nilfs_next_entry(de);

limit = nilfs_last_byte(dir, 0); /* is a multiple of chunk size */
if (unlikely(!limit || le64_to_cpu(de->inode) != dir->i_ino ||
!nilfs_match(1, ".", de))) {
msg = "missing '.'";
goto fail;
}

next_de = nilfs_next_entry(de);
/*
* If "next_de" has not reached the end of the chunk, there is
* at least one more record. Check whether it matches "..".
*/
if (unlikely((char *)next_de == (char *)de + nilfs_chunk_size(dir) ||
!nilfs_match(2, "..", next_de))) {
msg = "missing '..'";
goto fail;
}
*foliop = folio;
return next_de;

fail:
nilfs_error(dir->i_sb, "directory #%lu %s", dir->i_ino, msg);
folio_release_kmap(folio, de);
return NULL;
}

ino_t nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr)
Expand Down

0 comments on commit a9e1ddc

Please sign in to comment.