Skip to content

Commit

Permalink
nilfs2: reject devices with insufficient block count
Browse files Browse the repository at this point in the history
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/[email protected]
Signed-off-by: Ryusuke Konishi <[email protected]>
Reported-by: [email protected]
  Link: https://syzkaller.appspot.com/bug?extid=7d50f1e54a12ba3aeae2
Tested-by: Ryusuke Konishi <[email protected]>
Cc: <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Ryusuke Konishi <[email protected]>
  • Loading branch information
konis committed Jun 14, 2023
1 parent 6835fae commit 8a0105a
Showing 1 changed file with 42 additions and 1 deletion.
43 changes: 42 additions & 1 deletion fs/nilfs2/the_nilfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,18 @@ unsigned long nilfs_nrsvsegs(struct the_nilfs *nilfs, unsigned long nsegs)
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;
Expand All @@ -415,6 +427,8 @@ void nilfs_set_nsegments(struct the_nilfs *nilfs, unsigned long nsegs)
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).",
Expand Down Expand Up @@ -458,7 +472,34 @@ static int nilfs_store_disk_layout(struct the_nilfs *nilfs,
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;
}
Expand Down

0 comments on commit 8a0105a

Please sign in to comment.