nilfs2: prevent WARNING in nilfs_dat_commit_end()
authorRyusuke Konishi <konishi.ryusuke@gmail.com>
Fri, 27 Jan 2023 13:22:02 +0000 (22:22 +0900)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 3 Feb 2023 06:50:10 +0000 (22:50 -0800)
If nilfs2 reads a corrupted disk image and its DAT metadata file contains
invalid lifetime data for a virtual block number, a kernel warning can be
generated by the WARN_ON check in nilfs_dat_commit_end() and can panic if
the kernel is booted with panic_on_warn.

This patch avoids the issue with a sanity check that treats it as an
error.

Since error return is not allowed in the execution phase of
nilfs_dat_commit_end(), this inserts that sanity check in
nilfs_dat_prepare_end(), which prepares for nilfs_dat_commit_end().

As the error code, -EINVAL is returned to notify bmap layer of the
metadata corruption.  When the bmap layer sees this code, it handles the
abnormal situation and replaces the return code with -EIO as it should.

Link: https://lkml.kernel.org/r/000000000000154d2c05e9ec7df6@google.com
Link: https://lkml.kernel.org/r/20230127132202.6083-1-konishi.ryusuke@gmail.com
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Reported-by: <syzbot+cbff7a52b6f99059e67f@syzkaller.appspotmail.com>
Tested-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
fs/nilfs2/dat.c

index 1e7f653c1df7e0344a6d9ef3e9b446291fe7f13f..9cf6ba58f5859fe2a258c203e70921322fafab0a 100644 (file)
@@ -158,6 +158,7 @@ void nilfs_dat_commit_start(struct inode *dat, struct nilfs_palloc_req *req,
 int nilfs_dat_prepare_end(struct inode *dat, struct nilfs_palloc_req *req)
 {
        struct nilfs_dat_entry *entry;
+       __u64 start;
        sector_t blocknr;
        void *kaddr;
        int ret;
@@ -169,6 +170,7 @@ int nilfs_dat_prepare_end(struct inode *dat, struct nilfs_palloc_req *req)
        kaddr = kmap_atomic(req->pr_entry_bh->b_page);
        entry = nilfs_palloc_block_get_entry(dat, req->pr_entry_nr,
                                             req->pr_entry_bh, kaddr);
+       start = le64_to_cpu(entry->de_start);
        blocknr = le64_to_cpu(entry->de_blocknr);
        kunmap_atomic(kaddr);
 
@@ -179,6 +181,15 @@ int nilfs_dat_prepare_end(struct inode *dat, struct nilfs_palloc_req *req)
                        return ret;
                }
        }
+       if (unlikely(start > nilfs_mdt_cno(dat))) {
+               nilfs_err(dat->i_sb,
+                         "vblocknr = %llu has abnormal lifetime: start cno (= %llu) > current cno (= %llu)",
+                         (unsigned long long)req->pr_entry_nr,
+                         (unsigned long long)start,
+                         (unsigned long long)nilfs_mdt_cno(dat));
+               nilfs_dat_abort_entry(dat, req);
+               return -EINVAL;
+       }
 
        return 0;
 }