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

Reply via email to