From ce72ffb3285a72b661c99a60dea350d59cf85ee9 Mon Sep 17 00:00:00 2001 From: Hirohito Higashi Date: Sun, 6 Sep 2026 18:33:18 +0000 Subject: [PATCH] patch 9.2.1037: crash when slicing a range() list with too many items Problem: Vim crashes when slicing a list from range() that has more than 2147483647 items. range() also reports a start past the end for a valid range that spans the whole Number range, and len() changes after a list with a large {stride} is materialized (liuyi1231). Solution: Give E1510 when the number of items or {stride} does not fit in an int, and compute the length with unsigned arithmetic so it cannot overflow. Also stop list_slice() from using a NULL item, which 9.0.2123 made possible by re-checking the index after materializing (Hirohito Higashi). fixes: #21235 closes: #21236 Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- runtime/doc/builtin.txt | 4 ++- src/evalfunc.c | 55 +++++++++++++++++++++++++++++----- src/list.c | 3 +- src/testdir/test_functions.vim | 20 +++++++++++++ src/version.c | 2 ++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index e4e35752d6..c0dcf09e21 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.2. Last change: 2026 Sep 01 +*builtin.txt* For Vim version 9.2. Last change: 2026 Sep 06 VIM REFERENCE MANUAL by Bram Moolenaar @@ -8767,6 +8767,8 @@ range({expr} [, {max} [, {stride}]]) *range()* When the maximum is one before the start the result is an empty list. When the maximum is more than one before the start this is an error. + The list can have at most 2147483647 items and {stride} must + fit in a 32 bit number, otherwise |E1510| is given. Examples: > range(4) " [0, 1, 2, 3] range(2, 4) " [2, 3, 4] diff --git a/src/evalfunc.c b/src/evalfunc.c index 9083813de6..fce9ec796f 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -10363,12 +10363,56 @@ f_range(typval_T *argvars, typval_T *rettv) emsg(_(e_stride_is_zero)); return; } - if (stride > 0 ? end + 1 < start : end - 1 > start) + + // The stride is stored in "lv_stride", which is an int. + if (stride < INT_MIN || stride > INT_MAX) { - emsg(_(e_start_past_end)); + char buf[NUMBUFLEN]; + + vim_snprintf(buf, sizeof(buf), "%lld", stride); + semsg(_(e_val_too_large), buf); return; } + uvarnumber_T len; + + if (stride > 0 ? end < start : end > start) + { + // One step before the start gives an empty list, further away is + // an error. Subtract unsigned to avoid an overflow. + uvarnumber_T back = stride > 0 + ? (uvarnumber_T)start - (uvarnumber_T)end + : (uvarnumber_T)end - (uvarnumber_T)start; + + if (back > 1) + { + emsg(_(e_start_past_end)); + return; + } + len = 0; + } + else + { + varnumber_T astride = stride > 0 ? stride : -stride; + uvarnumber_T span = stride > 0 + ? (uvarnumber_T)end - (uvarnumber_T)start + : (uvarnumber_T)start - (uvarnumber_T)end; + uvarnumber_T count = span / (uvarnumber_T)astride; + + // The number of items is "count" + 1 and must fit in "lv_len". + if (count >= (uvarnumber_T)INT_MAX) + { + char buf[NUMBUFLEN]; + + // "count + 1" can wrap around. + vim_snprintf(buf, sizeof(buf), "%llu", + count < UVARNUM_MAX ? count + 1 : UVARNUM_MAX); + semsg(_(e_val_too_large), buf); + return; + } + len = count + 1; + } + list_T *list = rettv->vval.v_list; // Create a non-materialized list. This is much more efficient and @@ -10377,11 +10421,8 @@ f_range(typval_T *argvars, typval_T *rettv) list->lv_first = &range_list_item; list->lv_u.nonmat.lv_start = start; list->lv_u.nonmat.lv_end = end; - list->lv_u.nonmat.lv_stride = stride; - if (stride > 0 ? end < start : end > start) - list->lv_len = 0; - else - list->lv_len = (end - start) / stride + 1; + list->lv_u.nonmat.lv_stride = (int)stride; + list->lv_len = (int)len; } /* diff --git a/src/list.c b/src/list.c index 24efe6bcfc..76e93cd0cf 100644 --- a/src/list.c +++ b/src/list.c @@ -1303,7 +1303,8 @@ list_slice(list_T *ol, long n1, long n2) return NULL; for (item = list_find(ol, n1); n1 <= n2; ++n1) { - if (list_append_tv(l, &item->li_tv) == FAIL) + // "item" is NULL when materializing "ol" stopped early. + if (item == NULL || list_append_tv(l, &item->li_tv) == FAIL) { list_free(l); return NULL; diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index e3693c9136..1e12f32a97 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -3777,6 +3777,26 @@ func Test_range() call assert_fails('let x=range([])', 'E745:') call assert_fails('let x=range(1, [])', 'E745:') call assert_fails('let x=range(1, 4, [])', 'E745:') + + " the number of items must fit in an int + call assert_equal(2147483647, len(range(2147483647))) + call assert_fails('let x=range(2147483648)', + \ 'E1510: Value too large: 2147483648') + call assert_fails('let x=range(0, 2147483647)', + \ 'E1510: Value too large: 2147483648') + call assert_fails('let x=range(0, 4294967294, 2)', + \ 'E1510: Value too large: 2147483648') + call assert_fails('let x=range(0, 9223372036854775807)', + \ 'E1510: Value too large: 9223372036854775808') + + " the stride must fit in an int + call assert_fails('let x=range(0, 100, 3000000000)', + \ 'E1510: Value too large: 3000000000') + call assert_fails('let x=range(0, 100, -3000000000)', + \ 'E1510: Value too large: -3000000000') + + " slicing a range list with an overflowing length crashed + call assert_fails('let x=range(2147483648)[: 4]', 'E1510:') endfunc func Test_garbagecollect_now_fails() diff --git a/src/version.c b/src/version.c index 6843673fed..fea3366654 100644 --- a/src/version.c +++ b/src/version.c @@ -763,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1037, /**/ 1036, /**/