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:
1 parent
f1b4549129
commit
ce72ffb328
5 files changed
+75
-9
No files matched your search
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -763,6 +763,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1037,
|
||||
/**/
|
||||
1036,
|
||||
/**/
|
||||
|
||||
Reference in new issue
Block a user