Commit Message
Greg KH
Oct. 24, 2022, 11:27 a.m. UTC
From: Chao Yu <chao@kernel.org> commit c6ad7fd16657ebd34a87a97d9588195aae87597d upstream. As Wenqing Liu reported in bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=216456 BUG: KASAN: use-after-free in recover_data+0x63ae/0x6ae0 [f2fs] Read of size 4 at addr ffff8881464dcd80 by task mount/1013 CPU: 3 PID: 1013 Comm: mount Tainted: G W 6.0.0-rc4 #1 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.15.0-1 04/01/2014 Call Trace: dump_stack_lvl+0x45/0x5e print_report.cold+0xf3/0x68d kasan_report+0xa8/0x130 recover_data+0x63ae/0x6ae0 [f2fs] f2fs_recover_fsync_data+0x120d/0x1fc0 [f2fs] f2fs_fill_super+0x4665/0x61e0 [f2fs] mount_bdev+0x2cf/0x3b0 legacy_get_tree+0xed/0x1d0 vfs_get_tree+0x81/0x2b0 path_mount+0x47e/0x19d0 do_mount+0xce/0xf0 __x64_sys_mount+0x12c/0x1a0 do_syscall_64+0x38/0x90 entry_SYSCALL_64_after_hwframe+0x63/0xcd The root cause is: in fuzzed image, SSA table is corrupted: ofs_in_node is larger than ADDRS_PER_PAGE(), result in out-of-range access on 4k-size page. - recover_data - do_recover_data - check_index_in_prev_nodes - f2fs_data_blkaddr This patch adds sanity check on summary info in recovery and GC flow in where the flows rely on them. After patch: [ 29.310883] F2FS-fs (loop0): Inconsistent ofs_in_node:65286 in summary, ino:0, nid:6, max:1018 Cc: stable@vger.kernel.org Reported-by: Wenqing Liu <wenqingliu0120@gmail.com> Signed-off-by: Chao Yu <chao@kernel.org> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- fs/f2fs/gc.c | 10 +++++++++- fs/f2fs/recovery.c | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-)
Comments
Hi! > From: Chao Yu <chao@kernel.org> > > commit c6ad7fd16657ebd34a87a97d9588195aae87597d upstream. > > As Wenqing Liu reported in bugzilla: > > https://bugzilla.kernel.org/show_bug.cgi?id=216456 > > BUG: KASAN: use-after-free in recover_data+0x63ae/0x6ae0 [f2fs] > Read of size 4 at addr ffff8881464dcd80 by task mount/1013 I believe this is missing put_page on the error path: > +++ b/fs/f2fs/gc.c > @@ -1003,6 +1003,14 @@ static bool is_alive(struct f2fs_sb_info > return false; > } > > + max_addrs = IS_INODE(node_page) ? DEF_ADDRS_PER_INODE : > + DEF_ADDRS_PER_BLOCK; > + if (ofs_in_node >= max_addrs) { > + f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", > + ofs_in_node, dni->ino, dni->nid, max_addrs); > + return false; > + } > + > *nofs = ofs_of_node(node_page); > source_blkaddr = data_blkaddr(NULL, node_page, ofs_in_node); > f2fs_put_page(node_page, 1); So something like this is needed. (Feel free to test/adapt/apply). Signed-off-by: Pavel Machek <pavel@denx.de> Best regards, Pavel diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 4546e01b2ee0..dab794225cce 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -1110,6 +1110,7 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, if (ofs_in_node >= max_addrs) { f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", ofs_in_node, dni->ino, dni->nid, max_addrs); + f2fs_put_page(node_page, 1); return false; }
On 10/24, Pavel Machek wrote: > Hi! > > > From: Chao Yu <chao@kernel.org> > > > > commit c6ad7fd16657ebd34a87a97d9588195aae87597d upstream. > > > > As Wenqing Liu reported in bugzilla: > > > > https://bugzilla.kernel.org/show_bug.cgi?id=216456 > > > > BUG: KASAN: use-after-free in recover_data+0x63ae/0x6ae0 [f2fs] > > Read of size 4 at addr ffff8881464dcd80 by task mount/1013 > > I believe this is missing put_page on the error path: > > > +++ b/fs/f2fs/gc.c > > @@ -1003,6 +1003,14 @@ static bool is_alive(struct f2fs_sb_info > > return false; > > } > > > > + max_addrs = IS_INODE(node_page) ? DEF_ADDRS_PER_INODE : > > + DEF_ADDRS_PER_BLOCK; > > + if (ofs_in_node >= max_addrs) { > > + f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", > > + ofs_in_node, dni->ino, dni->nid, max_addrs); > > + return false; > > + } > > + > > *nofs = ofs_of_node(node_page); > > source_blkaddr = data_blkaddr(NULL, node_page, ofs_in_node); > > f2fs_put_page(node_page, 1); > > So something like this is needed. (Feel free to test/adapt/apply). Urg.. thank you so much for pointing this out. Applied the change to the tree. https://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs.git/commit/?h=dev&id=a22aeafb3d3569aecf811dca1aceff656695cdb4 > > Signed-off-by: Pavel Machek <pavel@denx.de> > > Best regards, > Pavel > > diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c > index 4546e01b2ee0..dab794225cce 100644 > --- a/fs/f2fs/gc.c > +++ b/fs/f2fs/gc.c > @@ -1110,6 +1110,7 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > if (ofs_in_node >= max_addrs) { > f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", > ofs_in_node, dni->ino, dni->nid, max_addrs); > + f2fs_put_page(node_page, 1); > return false; > } > > > -- > DENX Software Engineering GmbH, Managing Director: Wolfgang Denk > HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
On 2022/10/25 1:30, Pavel Machek wrote: > Hi! > >> From: Chao Yu <chao@kernel.org> >> >> commit c6ad7fd16657ebd34a87a97d9588195aae87597d upstream. >> >> As Wenqing Liu reported in bugzilla: >> >> https://bugzilla.kernel.org/show_bug.cgi?id=216456 >> >> BUG: KASAN: use-after-free in recover_data+0x63ae/0x6ae0 [f2fs] >> Read of size 4 at addr ffff8881464dcd80 by task mount/1013 > > I believe this is missing put_page on the error path: > >> +++ b/fs/f2fs/gc.c >> @@ -1003,6 +1003,14 @@ static bool is_alive(struct f2fs_sb_info >> return false; >> } >> >> + max_addrs = IS_INODE(node_page) ? DEF_ADDRS_PER_INODE : >> + DEF_ADDRS_PER_BLOCK; >> + if (ofs_in_node >= max_addrs) { >> + f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", >> + ofs_in_node, dni->ino, dni->nid, max_addrs); >> + return false; >> + } >> + >> *nofs = ofs_of_node(node_page); >> source_blkaddr = data_blkaddr(NULL, node_page, ofs_in_node); >> f2fs_put_page(node_page, 1); > > So something like this is needed. (Feel free to test/adapt/apply). > > Signed-off-by: Pavel Machek <pavel@denx.de> > > Best regards, > Pavel > > diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c > index 4546e01b2ee0..dab794225cce 100644 > --- a/fs/f2fs/gc.c > +++ b/fs/f2fs/gc.c > @@ -1110,6 +1110,7 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > if (ofs_in_node >= max_addrs) { > f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", > ofs_in_node, dni->ino, dni->nid, max_addrs); > + f2fs_put_page(node_page, 1); > return false; > } My bad, thanks for fixing this. Reviewed-by: Chao Yu <chao@kernel.org> Thanks, > >
--- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -977,7 +977,7 @@ static bool is_alive(struct f2fs_sb_info { struct page *node_page; nid_t nid; - unsigned int ofs_in_node; + unsigned int ofs_in_node, max_addrs; block_t source_blkaddr; nid = le32_to_cpu(sum->nid); @@ -1003,6 +1003,14 @@ static bool is_alive(struct f2fs_sb_info return false; } + max_addrs = IS_INODE(node_page) ? DEF_ADDRS_PER_INODE : + DEF_ADDRS_PER_BLOCK; + if (ofs_in_node >= max_addrs) { + f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%u, nid:%u, max:%u", + ofs_in_node, dni->ino, dni->nid, max_addrs); + return false; + } + *nofs = ofs_of_node(node_page); source_blkaddr = data_blkaddr(NULL, node_page, ofs_in_node); f2fs_put_page(node_page, 1); --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -437,7 +437,7 @@ static int check_index_in_prev_nodes(str struct dnode_of_data tdn = *dn; nid_t ino, nid; struct inode *inode; - unsigned int offset; + unsigned int offset, ofs_in_node, max_addrs; block_t bidx; int i; @@ -463,15 +463,24 @@ static int check_index_in_prev_nodes(str got_it: /* Use the locked dnode page and inode */ nid = le32_to_cpu(sum.nid); + ofs_in_node = le16_to_cpu(sum.ofs_in_node); + + max_addrs = ADDRS_PER_PAGE(dn->node_page, dn->inode); + if (ofs_in_node >= max_addrs) { + f2fs_err(sbi, "Inconsistent ofs_in_node:%u in summary, ino:%lu, nid:%u, max:%u", + ofs_in_node, dn->inode->i_ino, nid, max_addrs); + return -EFSCORRUPTED; + } + if (dn->inode->i_ino == nid) { tdn.nid = nid; if (!dn->inode_page_locked) lock_page(dn->inode_page); tdn.node_page = dn->inode_page; - tdn.ofs_in_node = le16_to_cpu(sum.ofs_in_node); + tdn.ofs_in_node = ofs_in_node; goto truncate_out; } else if (dn->nid == nid) { - tdn.ofs_in_node = le16_to_cpu(sum.ofs_in_node); + tdn.ofs_in_node = ofs_in_node; goto truncate_out; }