[PATCH] lib/cmdline: fix integer overflow of the index in get_options()

From: lzhan011

Date: Mon Oct 05 2026 - 16:25:10 EST


From: lzhan011 <zhangleizhen645@xxxxxxxxx>

For a range "M-N", get_options() advances its index by the size of the
range:

i += (range_nums - 1);

get_range() only bounds how many values it stores, but returns the full
size of the range, which can be as large as INT_MAX. The addition then
overflows i, which wraps to a negative value since the kernel is built
with -fno-strict-overflow. As a result:

- with nints > 0, the loop continues (i < nints still holds) and the
next get_option() call writes to ints[i], about 8 GiB below the
array;

- in validation mode (nints == 0), the returned count is negative or
bogus, so callers such as parse_int_array() allocate a too small
array, and the second get_options() pass then performs the same out
of bounds write.

For example, get_options("0-2147483647", 6, ints) writes to ints[INT_MIN].
parse_int_array() is reachable from several root-writable debugfs
files via parse_int_array_user() (e.g. in sound/soc/sof,
sound/soc/intel/avs, drivers/gpu/drm/xe and
drivers/platform/x86/intel/vsec_tpmi.c), and get_options() is used
directly by e.g. the lux_table sysfs store handlers in
drivers/iio/light/tsl2583.c and tsl2772.c.

Stop parsing if the range would overflow the index, and add a KUnit test
case. Ranges that don't overflow are parsed exactly as before.

Found by fuzzing get_options() and parse_int_array() with ASan/UBSan
in userspace; the new test case fails without this fix.

Fixes: 22f2e2801799 ("[PATCH] get_options to allow a hypenated range for isolcpus")
Assisted-by: Claude:claude-opus-5-5 ASan UBSan libFuzzer
Signed-off-by: lzhan011 <zhangleizhen645@xxxxxxxxx>
---
lib/cmdline.c | 5 ++++-
lib/tests/cmdline_kunit.c | 19 +++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/lib/cmdline.c b/lib/cmdline.c
index 16cce6621..76a4a39ec 100644
--- a/lib/cmdline.c
+++ b/lib/cmdline.c
@@ -125,8 +125,11 @@ char *get_options(const char *str, int nints, int *ints)
/*
* Decrement the result by one to leave out the
* last number in the range. The next iteration
- * will handle the upper number in the range
+ * will handle the upper number in the range.
+ * Stop if that would overflow @i.
*/
+ if (range_nums > INT_MAX - 1 - i)
+ break;
i += (range_nums - 1);
}
i++;
diff --git a/lib/tests/cmdline_kunit.c b/lib/tests/cmdline_kunit.c
index 3f61ff8d3..96e6593ff 100644
--- a/lib/tests/cmdline_kunit.c
+++ b/lib/tests/cmdline_kunit.c
@@ -252,12 +252,31 @@ static void cmdline_test_memparse(struct kunit *test)
" when parsing '%s'", e->input);
}
}
+static void cmdline_test_range_overflow(struct kunit *test)
+{
+ int r[16];
+
+ /*
+ * A range spanning (almost) the whole int space must not overflow
+ * the number of parsed integers; such a range is not counted.
+ * Only the validation mode is used, as it never writes beyond r[0].
+ */
+ r[0] = -1;
+ get_options("0-2147483647", 0, r);
+ KUNIT_EXPECT_EQ(test, r[0], 0);
+
+ r[0] = -1;
+ get_options("1,0-2147483647", 0, r);
+ KUNIT_EXPECT_EQ(test, r[0], 1);
+}
+

static struct kunit_case cmdline_test_cases[] = {
KUNIT_CASE(cmdline_test_noint),
KUNIT_CASE(cmdline_test_lead_int),
KUNIT_CASE(cmdline_test_tail_int),
KUNIT_CASE(cmdline_test_range),
+ KUNIT_CASE(cmdline_test_range_overflow),
KUNIT_CASE(cmdline_test_next_arg_quoted_value),
KUNIT_CASE(cmdline_test_next_arg_bare_quote_regression),
KUNIT_CASE(cmdline_test_next_arg_mixed_tokens),
--
2.34.1