nilfs2: fix shift-out-of-bounds due to too large exponent of block size
authorRyusuke Konishi <konishi.ryusuke@gmail.com>
Thu, 27 Oct 2022 04:43:06 +0000 (13:43 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 31 Dec 2022 12:14:40 +0000 (13:14 +0100)
[ Upstream commit ebeccaaef67a4895d2496ab8d9c2fb8d89201211 ]

If field s_log_block_size of superblock data is corrupted and too large,
init_nilfs() and load_nilfs() still can trigger a shift-out-of-bounds
warning followed by a kernel panic (if panic_on_warn is set):

 shift exponent 38973 is too large for 32-bit type 'int'
 Call Trace:
  <TASK>
  dump_stack_lvl+0xcd/0x134
  ubsan_epilogue+0xb/0x50
  __ubsan_handle_shift_out_of_bounds.cold.12+0x17b/0x1f5
  init_nilfs.cold.11+0x18/0x1d [nilfs2]
  nilfs_mount+0x9b5/0x12b0 [nilfs2]
  ...

This fixes the issue by adding and using a new helper function for getting
block size with sanity check.

Link: https://lkml.kernel.org/r/20221027044306.42774-3-konishi.ryusuke@gmail.com
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Tested-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/nilfs2/the_nilfs.c

index 6a86acd..6aa6cef 100644 (file)
@@ -194,6 +194,34 @@ static int nilfs_store_log_cursor(struct the_nilfs *nilfs,
 }
 
 /**
+ * nilfs_get_blocksize - get block size from raw superblock data
+ * @sb: super block instance
+ * @sbp: superblock raw data buffer
+ * @blocksize: place to store block size
+ *
+ * nilfs_get_blocksize() calculates the block size from the block size
+ * exponent information written in @sbp and stores it in @blocksize,
+ * or aborts with an error message if it's too large.
+ *
+ * Return Value: On success, 0 is returned. If the block size is too
+ * large, -EINVAL is returned.
+ */
+static int nilfs_get_blocksize(struct super_block *sb,
+                              struct nilfs_super_block *sbp, int *blocksize)
+{
+       unsigned int shift_bits = le32_to_cpu(sbp->s_log_block_size);
+
+       if (unlikely(shift_bits >
+                    ilog2(NILFS_MAX_BLOCK_SIZE) - BLOCK_SIZE_BITS)) {
+               nilfs_err(sb, "too large filesystem blocksize: 2 ^ %u KiB",
+                         shift_bits);
+               return -EINVAL;
+       }
+       *blocksize = BLOCK_SIZE << shift_bits;
+       return 0;
+}
+
+/**
  * load_nilfs - load and recover the nilfs
  * @nilfs: the_nilfs structure to be released
  * @sb: super block instance used to recover past segment
@@ -246,11 +274,15 @@ int load_nilfs(struct the_nilfs *nilfs, struct super_block *sb)
                nilfs->ns_sbwtime = le64_to_cpu(sbp[0]->s_wtime);
 
                /* verify consistency between two super blocks */
-               blocksize = BLOCK_SIZE << le32_to_cpu(sbp[0]->s_log_block_size);
+               err = nilfs_get_blocksize(sb, sbp[0], &blocksize);
+               if (err)
+                       goto scan_error;
+
                if (blocksize != nilfs->ns_blocksize) {
                        nilfs_warn(sb,
                                   "blocksize differs between two super blocks (%d != %d)",
                                   blocksize, nilfs->ns_blocksize);
+                       err = -EINVAL;
                        goto scan_error;
                }
 
@@ -609,9 +641,11 @@ int init_nilfs(struct the_nilfs *nilfs, struct super_block *sb, char *data)
        if (err)
                goto failed_sbh;
 
-       blocksize = BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size);
-       if (blocksize < NILFS_MIN_BLOCK_SIZE ||
-           blocksize > NILFS_MAX_BLOCK_SIZE) {
+       err = nilfs_get_blocksize(sb, sbp, &blocksize);
+       if (err)
+               goto failed_sbh;
+
+       if (blocksize < NILFS_MIN_BLOCK_SIZE) {
                nilfs_err(sb,
                          "couldn't mount because of unsupported filesystem blocksize %d",
                          blocksize);