[PATCH] dm cache: parse invalidate_cblocks with kstrtoull()

From: Samuel Moelius

Date: Sun Jun 28 2026 - 11:12:15 EST


invalidate_cblocks parses cache block numbers with sscanf() and then
stores them in the narrower dm_cblock_t type. Values larger than the
cblock representation are truncated before invalidation, so a request
for one cache block can invalidate a different block.

Checking the parsed value after sscanf() is not sufficient because
sscanf() does not reliably reject values beyond U64_MAX before storing
into the destination. Such inputs can still be converted to a wrapped
u64 value and then pass a later range check.

Parse each single value or range endpoint with kstrtoull() instead, and
reject values that do not fit in dm_cblock_t before converting them to
cblock values. The existing range validation continues to reject empty
or out-of-cache ranges, including the single-value U32_MAX case whose
exclusive end wraps to zero.

Assisted-by: Codex:gpt-5.5-cyber-preview
Signed-off-by: Samuel Moelius <sam.moelius@xxxxxxxxxxxxxxx>
---
drivers/md/dm-cache-target.c | 48 +++++++++++++++++++++++++++---------
1 file changed, 36 insertions(+), 12 deletions(-)

diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index 097315a9bf0f..35c1eea48116 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -16,6 +16,7 @@
#include <linux/dm-kcopyd.h>
#include <linux/jiffies.h>
#include <linux/init.h>
+#include <linux/kstrtox.h>
#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/rwsem.h>
@@ -3311,6 +3312,26 @@ struct cblock_range {
dm_cblock_t end;
};

+static int parse_cblock(const char *str, size_t len, uint64_t *result)
+{
+ char *buf;
+ int r;
+
+ if (!len)
+ return -EINVAL;
+
+ buf = kstrndup(str, len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ r = kstrtoull(buf, 10, result);
+ kfree(buf);
+ if (r)
+ return r;
+
+ return *result > U32_MAX ? -EINVAL : 0;
+}
+
/*
* A cache block range can take two forms:
*
@@ -3320,32 +3341,35 @@ struct cblock_range {
static int parse_cblock_range(struct cache *cache, const char *str,
struct cblock_range *result)
{
- char dummy;
+ const char *dash = strchr(str, '-');
uint64_t b, e;
int r;

- /*
- * Try and parse form (ii) first.
- */
- r = sscanf(str, "%llu-%llu%c", &b, &e, &dummy);
+ if (dash) {
+ r = parse_cblock(str, dash - str, &b);
+ if (r)
+ goto bad;
+
+ r = parse_cblock(dash + 1, strlen(dash + 1), &e);
+ if (r)
+ goto bad;

- if (r == 2) {
result->begin = to_cblock(b);
result->end = to_cblock(e);
return 0;
}

- /*
- * That didn't work, try form (i).
- */
- r = sscanf(str, "%llu%c", &b, &dummy);
-
- if (r == 1) {
+ r = parse_cblock(str, strlen(str), &b);
+ if (!r) {
result->begin = to_cblock(b);
result->end = to_cblock(from_cblock(result->begin) + 1u);
return 0;
}

+bad:
+ if (r == -ENOMEM)
+ return r;
+
DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), str);
return -EINVAL;
}
--
2.43.0