[PATCH v2] dm cache: Validate the discard block size before narrowing it
From: Kirtikumar Anandrao Ramchandani
Date: Wed Nov 04 2026 - 21:11:29 EST
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: syzbot+c8564fc0a96e3ecbe4d4@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=c8564fc0a96e3ecbe4d4
Signed-off-by: Kirtikumar Anandrao Ramchandani <kirtiar15502@xxxxxxxxx>
---
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