]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0236: stack-overflow with deeply nested data in json_encode/decode() v9.2.0236
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Mon, 23 Mar 2026 21:42:04 +0000 (21:42 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 23 Mar 2026 21:42:04 +0000 (21:42 +0000)
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>
runtime/doc/options.txt
src/errors.h
src/json.c
src/json_test.c
src/testdir/test_json.vim
src/userfunc.c
src/version.c

index d286467787447d7cb9d29c2d8659c825e9729156..5f30fb715ea39ba782e79e8d991b2cfd02cfc95f 100644 (file)
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.2.  Last change: 2026 Mar 22
+*options.txt*  For Vim version 9.2.  Last change: 2026 Mar 23
 
 
                  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|.
        Increasing this limit above 200 also changes the maximum for Ex
        command recursion, see |E169|.
        See also |:function|.
-       Also used for maximum depth of callback functions.
+       Also used for maximum depth of callback functions and encoding and
+       decoding of deeply nested json data.
 
                                                *'maxmapdepth'* *'mmd'* *E223*
 'maxmapdepth' 'mmd'    number  (default 1000)
index d780fb4207ea6db66555b7209adabb702eb3782d..016b917bdfac1bb4920fe4d73753d58a9f39600f 100644 (file)
@@ -312,7 +312,7 @@ EXTERN char e_function_name_required[]
 // E130 unused
 EXTERN char e_cannot_delete_function_str_it_is_in_use[]
        INIT(= N_("E131: Cannot delete function %s: It is in use"));
-EXTERN char e_function_call_depth_is_higher_than_macfuncdepth[]
+EXTERN char e_function_call_depth_is_higher_than_maxfuncdepth[]
        INIT(= N_("E132: Function call depth is higher than 'maxfuncdepth'"));
 EXTERN char e_return_not_inside_function[]
        INIT(= N_("E133: :return not inside a function"));
index 8b0b050b8ab703fbe542e7ac1a84a31c186e9b5d..7c4874e5ae294e766467556ba515ece37ea4a763 100644 (file)
@@ -18,7 +18,7 @@
 
 #if defined(FEAT_EVAL)
 
-static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
+static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth);
 
 /*
  * 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
     static int
 json_encode_gap(garray_T *gap, typval_T *val, int options)
 {
-    if (json_encode_item(gap, val, get_copyID(), options) == FAIL)
+    if (json_encode_item(gap, val, get_copyID(), options, 0) == FAIL)
     {
        ga_clear(gap);
        gap->ga_data = vim_strsave((char_u *)"");
@@ -268,7 +268,7 @@ is_simple_key(char_u *key)
  * Return FAIL or OK.
  */
     static int
-json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
+json_encode_item(garray_T *gap, typval_T *val, int copyID, int options, int depth)
 {
     char_u     numbuf[NUMBUFLEN];
     char_u     *res;
@@ -278,6 +278,12 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
     dict_T     *d;
     int                i;
 
+    if (depth > p_mfd)
+    {
+       emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
+       return FAIL;
+    }
+
     switch (val->v_type)
     {
        case VAR_BOOL:
@@ -365,7 +371,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
                    for (li = l->lv_first; li != NULL && !got_int; )
                    {
                        if (json_encode_item(gap, &li->li_tv, copyID,
-                                                  options & JSON_JS) == FAIL)
+                                                  options & JSON_JS,
+                                                  depth + 1) == FAIL)
                            return FAIL;
                        if ((options & JSON_JS)
                                && li->li_next == NULL
@@ -401,7 +408,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
                    {
                        typval_T        *t_item = TUPLE_ITEM(tuple, i);
                        if (json_encode_item(gap, t_item, copyID,
-                                                  options & JSON_JS) == FAIL)
+                                                  options & JSON_JS,
+                                                  depth + 1) == FAIL)
                            return FAIL;
 
                        if ((options & JSON_JS)
@@ -452,7 +460,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
                                write_string(gap, hi->hi_key);
                            ga_append(gap, ':');
                            if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
-                                     copyID, options | JSON_NO_NONE) == FAIL)
+                                     copyID, options | JSON_NO_NONE,
+                                     depth + 1) == FAIL)
                                return FAIL;
                        }
                    ga_append(gap, '}');
@@ -807,6 +816,12 @@ json_decode_item(js_read_T *reader, typval_T *res, int options)
                        retval = FAIL;
                        break;
                    }
+                   if (stack.ga_len >= p_mfd)
+                   {
+                       emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
+                       retval = FAIL;
+                       break;
+                   }
                    if (ga_grow(&stack, 1) == FAIL)
                    {
                        retval = FAIL;
@@ -838,6 +853,12 @@ json_decode_item(js_read_T *reader, typval_T *res, int options)
                        retval = FAIL;
                        break;
                    }
+                   if (stack.ga_len >= p_mfd)
+                   {
+                       emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
+                       retval = FAIL;
+                       break;
+                   }
                    if (ga_grow(&stack, 1) == FAIL)
                    {
                        retval = FAIL;
index 5fb772ee05081e6fe32f50a74d37121435447d13..a35fd75a4a23da1f6e889573c375fc243f516401 100644 (file)
@@ -195,6 +195,7 @@ test_fill_called_on_string(void)
 main(void)
 {
 #if defined(FEAT_EVAL)
+    p_mfd = 100;
     test_decode_find_end();
     test_fill_called_on_find_end();
     test_fill_called_on_string();
index 96eddc23d5e0a7f19e32e6007518b2b0220169e7..515ce9b38c13487a16b1942584129c8c5c6280c0 100644 (file)
@@ -326,4 +326,34 @@ func Test_json_encode_long()
   call assert_equal(4000, len(json))
 endfunc
 
+func Test_json_encode_depth()
+  let save_mfd = &maxfuncdepth
+  set maxfuncdepth=10
+
+  " Create a deeply nested list that exceeds maxfuncdepth.
+  let l = []
+  let d = {}
+  for i in range(20)
+    let l = [l]
+    let d = {1: d}
+  endfor
+  call assert_fails('call json_encode(l)', 'E132:')
+  call assert_fails('call json_encode(d)', 'E132:')
+
+  let &maxfuncdepth = save_mfd
+endfunc
+
+func Test_json_decode_depth()
+  let save_mfd = &maxfuncdepth
+  set maxfuncdepth=10
+
+  let deep_json = repeat('[', 20) .. '1' .. repeat(']', 20)
+  call assert_fails('call json_decode(deep_json)', 'E132:')
+
+  let deep_json = repeat('{"a":', 20) .. '1' .. repeat('}', 20)
+  call assert_fails('call json_decode(deep_json)', 'E132:')
+
+  let &maxfuncdepth = save_mfd
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 0d7cf23a0babf583d73c5aa37bf5ab4ba0041e39..9de6bdbaf46dae512cfd0c36440182327eb4830f 100644 (file)
@@ -2910,7 +2910,7 @@ funcdepth_increment(void)
 {
     if (funcdepth >= p_mfd)
     {
-       emsg(_(e_function_call_depth_is_higher_than_macfuncdepth));
+       emsg(_(e_function_call_depth_is_higher_than_maxfuncdepth));
        return FAIL;
     }
     ++funcdepth;
index 1fdb6f68f867b331d7e2fa0b4779d8e63e291fa9..80680b4264a79684d5f1b025b887054b09251587 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    236,
 /**/
     235,
 /**/