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) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito HigashiandClaude Opus 5 authored and Christian Brabandt committed 2026-09-06 18:36:06 +00:00
1 parent f1b4549129
commit ce72ffb328
5 files changed
+75 -9

No files matched your search

+3 -1
View File
@@ -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]
+48 -7
View File
@@ -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;
}
/*
+2 -1
View File
@@ -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;
+20
View File
@@ -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()
+2
View File
@@ -763,6 +763,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1037,
/**/
1036,
/**/