The discard block size doubles until the origin fits within the discard bitset limit. too_many_discard_blocks() passes this sector_t size to sector_div(), whose divisor is only 32 bits. Once the size exceeds that range, the division uses a truncated divisor, which can become zero.
Use a 64-bit divisor when checking the block count. The selected size is stored in a u32, so reject sizes that cannot be represented before the assignment and before calculating the bitset size. This prevents a truncated zero size from reaching dm_sector_div_up(). An earlier proposal checks whether the mapped length exceeds the origin device. This fix also covers an origin device whose valid size requires a discard block size beyond the field's range. Reported-by: [email protected] Closes: https://syzkaller.appspot.com/bug?extid=c8564fc0a96e3ecbe4d4 Signed-off-by: Kirtikumar Anandrao Ramchandani <[email protected]> --- Changes in v2: - Resend as plain text after the previous mail client reformatted the diff. drivers/md/dm-cache-target.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 1a50724..7bacd88 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -17,6 +17,7 @@ #include <linux/jiffies.h> #include <linux/init.h> #include <linux/kstrtox.h> +#include <linux/math64.h> #include <linux/mempool.h> #include <linux/module.h> #include <linux/rwsem.h> @@ -2383,9 +2384,7 @@ static int create_cache_policy(struct cache *cache, struct cache_args *ca, static bool too_many_discard_blocks(sector_t discard_block_size, sector_t origin_size) { - (void) sector_div(origin_size, discard_block_size); - - return origin_size > MAX_DISCARD_BLOCKS; + return div64_u64(origin_size, discard_block_size) > MAX_DISCARD_BLOCKS; } static sector_t calculate_discard_block_size(sector_t cache_block_size, @@ -2422,6 +2421,7 @@ static int cache_create(struct cache_args *ca, struct cache **result) struct cache *cache; struct dm_target *ti = ca->ti; dm_block_t origin_blocks; + sector_t discard_block_size; struct dm_cache_metadata *cmd; bool may_format = ca->features.mode == CM_WRITE; @@ -2521,9 +2521,15 @@ static int cache_create(struct cache_args *ca, struct cache **result) } clear_bitset(cache->dirty_bitset, from_cblock(cache->cache_size)); - cache->discard_block_size = + discard_block_size = calculate_discard_block_size(cache->sectors_per_block, cache->origin_sectors); + if (discard_block_size > U32_MAX) { + *error = "Discard block size exceeds 32 bits"; + r = -EOVERFLOW; + goto bad; + } + cache->discard_block_size = discard_block_size; cache->discard_nr_blocks = to_dblock(dm_sector_div_up(cache->origin_sectors, cache->discard_block_size)); cache->discard_bitset = alloc_bitset(from_dblock(cache->discard_nr_blocks)); base-commit: 05441d697f7f9a927264f4058cbbc6a03dfcac9d
