patch 9.2.0236: stack-overflow with deeply nested data in json_encode/decode()

Commit: 
https://github.com/vim/vim/commit/abd2d7d4532c5687736b88718863163618f13c13
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Mon Mar 23 21:42:04 2026 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index d28646778..5f30fb715 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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)
diff --git a/src/errors.h b/src/errors.h
index d780fb420..016b917bd 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -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"));
diff --git a/src/json.c b/src/json.c
index 8b0b050b8..7c4874e5a 100644
--- a/src/json.c
+++ b/src/json.c
@@ -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;
diff --git a/src/json_test.c b/src/json_test.c
index 5fb772ee0..a35fd75a4 100644
--- a/src/json_test.c
+++ b/src/json_test.c
@@ -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();
diff --git a/src/testdir/test_json.vim b/src/testdir/test_json.vim
index 96eddc23d..515ce9b38 100644
--- a/src/testdir/test_json.vim
+++ b/src/testdir/test_json.vim
@@ -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
diff --git a/src/userfunc.c b/src/userfunc.c
index 0d7cf23a0..9de6bdbaf 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -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;
diff --git a/src/version.c b/src/version.c
index 1fdb6f68f..80680b426 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    236,
 /**/
     235,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1w57S1-0047Eq-Af%40256bit.org.

Raspunde prin e-mail lui