| From: Ryusuke Konishi <konishi.ryusuke@gmail.com> |
| Subject: nilfs2: reject devices with insufficient block count |
| Date: Fri, 26 May 2023 11:13:32 +0900 |
| |
| The current sanity check for nilfs2 geometry information lacks checks for |
| the number of segments stored in superblocks, so even for device images |
| that have been destructively truncated or have an unusually high number of |
| segments, the mount operation may succeed. |
| |
| This causes out-of-bounds block I/O on file system block reads or log |
| writes to the segments, the latter in particular causing |
| "a_ops->writepages" to repeatedly fail, resulting in sync_inodes_sb() to |
| hang. |
| |
| Fix this issue by checking the number of segments stored in the superblock |
| and avoiding mounting devices that can cause out-of-bounds accesses. To |
| eliminate the possibility of overflow when calculating the number of |
| blocks required for the device from the number of segments, this also adds |
| a helper function to calculate the upper bound on the number of segments |
| and inserts a check using it. |
| |
| Link: https://lkml.kernel.org/r/20230526021332.3431-1-konishi.ryusuke@gmail.com |
| Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com> |
| Reported-by: syzbot+7d50f1e54a12ba3aeae2@syzkaller.appspotmail.com |
| Link: https://syzkaller.appspot.com/bug?extid=7d50f1e54a12ba3aeae2 |
| Tested-by: Ryusuke Konishi <konishi.ryusuke@gmail.com> |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| --- |
| |
| fs/nilfs2/the_nilfs.c | 43 +++++++++++++++++++++++++++++++++++++++- |
| 1 file changed, 42 insertions(+), 1 deletion(-) |
| |
| --- a/fs/nilfs2/the_nilfs.c~nilfs2-reject-devices-with-insufficient-block-count |
| +++ a/fs/nilfs2/the_nilfs.c |
| @@ -405,6 +405,18 @@ unsigned long nilfs_nrsvsegs(struct the_ |
| 100)); |
| } |
| |
| +/** |
| + * nilfs_max_segment_count - calculate the maximum number of segments |
| + * @nilfs: nilfs object |
| + */ |
| +static u64 nilfs_max_segment_count(struct the_nilfs *nilfs) |
| +{ |
| + u64 max_count = U64_MAX; |
| + |
| + do_div(max_count, nilfs->ns_blocks_per_segment); |
| + return min_t(u64, max_count, ULONG_MAX); |
| +} |
| + |
| void nilfs_set_nsegments(struct the_nilfs *nilfs, unsigned long nsegs) |
| { |
| nilfs->ns_nsegments = nsegs; |
| @@ -414,6 +426,8 @@ void nilfs_set_nsegments(struct the_nilf |
| static int nilfs_store_disk_layout(struct the_nilfs *nilfs, |
| struct nilfs_super_block *sbp) |
| { |
| + u64 nsegments, nblocks; |
| + |
| if (le32_to_cpu(sbp->s_rev_level) < NILFS_MIN_SUPP_REV) { |
| nilfs_err(nilfs->ns_sb, |
| "unsupported revision (superblock rev.=%d.%d, current rev.=%d.%d). Please check the version of mkfs.nilfs(2).", |
| @@ -457,7 +471,34 @@ static int nilfs_store_disk_layout(struc |
| return -EINVAL; |
| } |
| |
| - nilfs_set_nsegments(nilfs, le64_to_cpu(sbp->s_nsegments)); |
| + nsegments = le64_to_cpu(sbp->s_nsegments); |
| + if (nsegments > nilfs_max_segment_count(nilfs)) { |
| + nilfs_err(nilfs->ns_sb, |
| + "segment count %llu exceeds upper limit (%llu segments)", |
| + (unsigned long long)nsegments, |
| + (unsigned long long)nilfs_max_segment_count(nilfs)); |
| + return -EINVAL; |
| + } |
| + |
| + nblocks = sb_bdev_nr_blocks(nilfs->ns_sb); |
| + if (nblocks) { |
| + u64 min_block_count = nsegments * nilfs->ns_blocks_per_segment; |
| + /* |
| + * To avoid failing to mount early device images without a |
| + * second superblock, exclude that block count from the |
| + * "min_block_count" calculation. |
| + */ |
| + |
| + if (nblocks < min_block_count) { |
| + nilfs_err(nilfs->ns_sb, |
| + "total number of segment blocks %llu exceeds device size (%llu blocks)", |
| + (unsigned long long)min_block_count, |
| + (unsigned long long)nblocks); |
| + return -EINVAL; |
| + } |
| + } |
| + |
| + nilfs_set_nsegments(nilfs, nsegments); |
| nilfs->ns_crc_seed = le32_to_cpu(sbp->s_crc_seed); |
| return 0; |
| } |
| _ |