Skip to content

Commit abd2d7d

Browse files
mattnchrisbra
authored andcommitted
patch 9.2.0236: stack-overflow with deeply nested data in json_encode/decode()
Problem: stack-overflow with deeply nested data in json_encode/decode() (ZyX-I) Solution: Add depth limit check using 'maxfuncdepth' to json_encode_item() and json_decode_item() to avoid crash when encoding/decoding deeply nested lists, dicts, or JSON arrays/objects, fix typo in error name, add tests (Yasuhiro Matsumoto). fixes: #588 closes: #19808 Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent c9eaff0 commit abd2d7d

7 files changed

Lines changed: 65 additions & 10 deletions

File tree

runtime/doc/options.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*options.txt* For Vim version 9.2. Last change: 2026 Mar 22
1+
*options.txt* For Vim version 9.2. Last change: 2026 Mar 23
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -6026,7 +6026,8 @@ A jump table for the options with a short description can be found at |Q_op|.
60266026
Increasing this limit above 200 also changes the maximum for Ex
60276027
command recursion, see |E169|.
60286028
See also |:function|.
6029-
Also used for maximum depth of callback functions.
6029+
Also used for maximum depth of callback functions and encoding and
6030+
decoding of deeply nested json data.
60306031

60316032
*'maxmapdepth'* *'mmd'* *E223*
60326033
'maxmapdepth' 'mmd' number (default 1000)

src/errors.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ EXTERN char e_function_name_required[]
312312
// E130 unused
313313
EXTERN char e_cannot_delete_function_str_it_is_in_use[]
314314
INIT(= N_("E131: Cannot delete function %s: It is in use"));
315-
EXTERN char e_function_call_depth_is_higher_than_macfuncdepth[]
315+
EXTERN char e_function_call_depth_is_higher_than_maxfuncdepth[]
316316
INIT(= N_("E132: Function call depth is higher than 'maxfuncdepth'"));
317317
EXTERN char e_return_not_inside_function[]
318318
INIT(= N_("E133: :return not inside a function"));

src/json.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
#if defined(FEAT_EVAL)
2020

21-
static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
21+
static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth);
2222

2323
/*
2424
* Encode "val" into a JSON format string.
@@ -28,7 +28,7 @@ static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int option
2828
static int
2929
json_encode_gap(garray_T *gap, typval_T *val, int options)
3030
{
31-
if (json_encode_item(gap, val, get_copyID(), options) == FAIL)
31+
if (json_encode_item(gap, val, get_copyID(), options, 0) == FAIL)
3232
{
3333
ga_clear(gap);
3434
gap->ga_data = vim_strsave((char_u *)"");
@@ -268,7 +268,7 @@ is_simple_key(char_u *key)
268268
* Return FAIL or OK.
269269
*/
270270
static int
271-
json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
271+
json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth)
272272
{
273273
char_u numbuf[NUMBUFLEN];
274274
char_u *res;
@@ -278,6 +278,12 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
278278
dict_T *d;
279279
int i;
280280

281+
if (depth > p_mfd)
282+
{
283+
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
284+
return FAIL;
285+
}
286+
281287
switch (val->v_type)
282288
{
283289
case VAR_BOOL:
@@ -365,7 +371,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
365371
for (li = l->lv_first; li != NULL && !got_int; )
366372
{
367373
if (json_encode_item(gap, &li->li_tv, copyID,
368-
options & JSON_JS) == FAIL)
374+
options & JSON_JS,
375+
depth + 1) == FAIL)
369376
return FAIL;
370377
if ((options & JSON_JS)
371378
&& li->li_next == NULL
@@ -401,7 +408,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
401408
{
402409
typval_T *t_item = TUPLE_ITEM(tuple, i);
403410
if (json_encode_item(gap, t_item, copyID,
404-
options & JSON_JS) == FAIL)
411+
options & JSON_JS,
412+
depth + 1) == FAIL)
405413
return FAIL;
406414

407415
if ((options & JSON_JS)
@@ -452,7 +460,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
452460
write_string(gap, hi->hi_key);
453461
ga_append(gap, ':');
454462
if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
455-
copyID, options | JSON_NO_NONE) == FAIL)
463+
copyID, options | JSON_NO_NONE,
464+
depth + 1) == FAIL)
456465
return FAIL;
457466
}
458467
ga_append(gap, '}');
@@ -807,6 +816,12 @@ json_decode_item(js_read_T *reader, typval_T *res, int options)
807816
retval = FAIL;
808817
break;
809818
}
819+
if (stack.ga_len >= p_mfd)
820+
{
821+
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
822+
retval = FAIL;
823+
break;
824+
}
810825
if (ga_grow(&stack, 1) == FAIL)
811826
{
812827
retval = FAIL;
@@ -838,6 +853,12 @@ json_decode_item(js_read_T *reader, typval_T *res, int options)
838853
retval = FAIL;
839854
break;
840855
}
856+
if (stack.ga_len >= p_mfd)
857+
{
858+
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
859+
retval = FAIL;
860+
break;
861+
}
841862
if (ga_grow(&stack, 1) == FAIL)
842863
{
843864
retval = FAIL;

src/json_test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ test_fill_called_on_string(void)
195195
main(void)
196196
{
197197
#if defined(FEAT_EVAL)
198+
p_mfd = 100;
198199
test_decode_find_end();
199200
test_fill_called_on_find_end();
200201
test_fill_called_on_string();

src/testdir/test_json.vim

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,34 @@ func Test_json_encode_long()
326326
call assert_equal(4000, len(json))
327327
endfunc
328328

329+
func Test_json_encode_depth()
330+
let save_mfd = &maxfuncdepth
331+
set maxfuncdepth=10
332+
333+
" Create a deeply nested list that exceeds maxfuncdepth.
334+
let l = []
335+
let d = {}
336+
for i in range(20)
337+
let l = [l]
338+
let d = {1: d}
339+
endfor
340+
call assert_fails('call json_encode(l)', 'E132:')
341+
call assert_fails('call json_encode(d)', 'E132:')
342+
343+
let &maxfuncdepth = save_mfd
344+
endfunc
345+
346+
func Test_json_decode_depth()
347+
let save_mfd = &maxfuncdepth
348+
set maxfuncdepth=10
349+
350+
let deep_json = repeat('[', 20) .. '1' .. repeat(']', 20)
351+
call assert_fails('call json_decode(deep_json)', 'E132:')
352+
353+
let deep_json = repeat('{"a":', 20) .. '1' .. repeat('}', 20)
354+
call assert_fails('call json_decode(deep_json)', 'E132:')
355+
356+
let &maxfuncdepth = save_mfd
357+
endfunc
358+
329359
" vim: shiftwidth=2 sts=2 expandtab

src/userfunc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2910,7 +2910,7 @@ funcdepth_increment(void)
29102910
{
29112911
if (funcdepth >= p_mfd)
29122912
{
2913-
emsg(_(e_function_call_depth_is_higher_than_macfuncdepth));
2913+
emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
29142914
return FAIL;
29152915
}
29162916
++funcdepth;

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
236,
737739
/**/
738740
235,
739741
/**/

0 commit comments

Comments
 (0)